Add Import CSV and Export CSV Permissions (#13421)

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-25 11:37:38 +05:30
committed by GitHub
parent f411bd1b0e
commit 4b95de6775
28 changed files with 118 additions and 71 deletions

View File

@ -1888,6 +1888,8 @@ export enum PermissionFlagType {
ADMIN_PANEL = 'ADMIN_PANEL', ADMIN_PANEL = 'ADMIN_PANEL',
API_KEYS_AND_WEBHOOKS = 'API_KEYS_AND_WEBHOOKS', API_KEYS_AND_WEBHOOKS = 'API_KEYS_AND_WEBHOOKS',
DATA_MODEL = 'DATA_MODEL', DATA_MODEL = 'DATA_MODEL',
EXPORT_CSV = 'EXPORT_CSV',
IMPORT_CSV = 'IMPORT_CSV',
ROLES = 'ROLES', ROLES = 'ROLES',
SECURITY = 'SECURITY', SECURITY = 'SECURITY',
SEND_EMAIL_TOOL = 'SEND_EMAIL_TOOL', SEND_EMAIL_TOOL = 'SEND_EMAIL_TOOL',
@ -2836,7 +2838,7 @@ export type UserWorkspace = {
objectPermissions?: Maybe<Array<ObjectPermission>>; objectPermissions?: Maybe<Array<ObjectPermission>>;
/** @deprecated Use objectPermissions instead */ /** @deprecated Use objectPermissions instead */
objectRecordsPermissions?: Maybe<Array<PermissionsOnAllObjectRecords>>; objectRecordsPermissions?: Maybe<Array<PermissionsOnAllObjectRecords>>;
settingsPermissions?: Maybe<Array<PermissionFlagType>>; permissionFlags?: Maybe<Array<PermissionFlagType>>;
twoFactorAuthenticationMethodSummary?: Maybe<Array<TwoFactorAuthenticationMethodDto>>; twoFactorAuthenticationMethodSummary?: Maybe<Array<TwoFactorAuthenticationMethodDto>>;
updatedAt: Scalars['DateTime']; updatedAt: Scalars['DateTime'];
user: User; user: User;
@ -3839,7 +3841,7 @@ export type VerifyTwoFactorAuthenticationMethodForAuthenticatedUserMutationVaria
export type VerifyTwoFactorAuthenticationMethodForAuthenticatedUserMutation = { __typename?: 'Mutation', verifyTwoFactorAuthenticationMethodForAuthenticatedUser: { __typename?: 'VerifyTwoFactorAuthenticationMethodOutput', success: boolean } }; export type VerifyTwoFactorAuthenticationMethodForAuthenticatedUserMutation = { __typename?: 'Mutation', verifyTwoFactorAuthenticationMethodForAuthenticatedUser: { __typename?: 'VerifyTwoFactorAuthenticationMethodOutput', success: boolean } };
export type UserQueryFragmentFragment = { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars?: any | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, deletedWorkspaceMembers?: Array<{ __typename?: 'DeletedWorkspaceMember', id: any, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', settingsPermissions?: Array<PermissionFlagType> | null, objectRecordsPermissions?: Array<PermissionsOnAllObjectRecords> | null, objectPermissions?: Array<{ __typename?: 'ObjectPermission', objectMetadataId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null }> | null, twoFactorAuthenticationMethodSummary?: Array<{ __typename?: 'TwoFactorAuthenticationMethodDTO', twoFactorAuthenticationMethodId: any, status: string, strategy: string }> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, customDomain?: string | null, isCustomDomainEnabled: boolean, metadataVersion: number, workspaceMembersCount?: number | null, isTwoFactorAuthenticationEnforced: boolean, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlagDTO', key: FeatureFlagKey, value: boolean }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null, metadata: any, billingSubscriptionItems?: Array<{ __typename?: 'BillingSubscriptionItem', id: any, hasReachedCurrentPeriodCap: boolean, quantity?: number | null, billingProduct?: { __typename?: 'BillingProduct', name: string, description: string, metadata: { __typename?: 'BillingProductMetadata', planKey: BillingPlanKey, priceUsageBased: BillingUsageType, productKey: BillingProductKey } } | null }> | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, metadata: any }>, defaultRole?: { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, canAccessAllTools: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean } | null, defaultAgent?: { __typename?: 'Agent', id: any } | null } | null, availableWorkspaces: { __typename?: 'AvailableWorkspaces', availableWorkspacesForSignIn: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }>, availableWorkspacesForSignUp: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }> } }; export type UserQueryFragmentFragment = { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars?: any | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, deletedWorkspaceMembers?: Array<{ __typename?: 'DeletedWorkspaceMember', id: any, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', permissionFlags?: Array<PermissionFlagType> | null, objectRecordsPermissions?: Array<PermissionsOnAllObjectRecords> | null, objectPermissions?: Array<{ __typename?: 'ObjectPermission', objectMetadataId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null }> | null, twoFactorAuthenticationMethodSummary?: Array<{ __typename?: 'TwoFactorAuthenticationMethodDTO', twoFactorAuthenticationMethodId: any, status: string, strategy: string }> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, customDomain?: string | null, isCustomDomainEnabled: boolean, metadataVersion: number, workspaceMembersCount?: number | null, isTwoFactorAuthenticationEnforced: boolean, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlagDTO', key: FeatureFlagKey, value: boolean }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null, metadata: any, billingSubscriptionItems?: Array<{ __typename?: 'BillingSubscriptionItem', id: any, hasReachedCurrentPeriodCap: boolean, quantity?: number | null, billingProduct?: { __typename?: 'BillingProduct', name: string, description: string, metadata: { __typename?: 'BillingProductMetadata', planKey: BillingPlanKey, priceUsageBased: BillingUsageType, productKey: BillingProductKey } } | null }> | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, metadata: any }>, defaultRole?: { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, canAccessAllTools: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean } | null, defaultAgent?: { __typename?: 'Agent', id: any } | null } | null, availableWorkspaces: { __typename?: 'AvailableWorkspaces', availableWorkspacesForSignIn: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }>, availableWorkspacesForSignUp: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }> } };
export type WorkspaceUrlsFragmentFragment = { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }; export type WorkspaceUrlsFragmentFragment = { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null };
@ -3858,7 +3860,7 @@ export type UploadProfilePictureMutation = { __typename?: 'Mutation', uploadProf
export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never; }>; export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never; }>;
export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars?: any | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, deletedWorkspaceMembers?: Array<{ __typename?: 'DeletedWorkspaceMember', id: any, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', settingsPermissions?: Array<PermissionFlagType> | null, objectRecordsPermissions?: Array<PermissionsOnAllObjectRecords> | null, objectPermissions?: Array<{ __typename?: 'ObjectPermission', objectMetadataId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null }> | null, twoFactorAuthenticationMethodSummary?: Array<{ __typename?: 'TwoFactorAuthenticationMethodDTO', twoFactorAuthenticationMethodId: any, status: string, strategy: string }> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, customDomain?: string | null, isCustomDomainEnabled: boolean, metadataVersion: number, workspaceMembersCount?: number | null, isTwoFactorAuthenticationEnforced: boolean, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlagDTO', key: FeatureFlagKey, value: boolean }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null, metadata: any, billingSubscriptionItems?: Array<{ __typename?: 'BillingSubscriptionItem', id: any, hasReachedCurrentPeriodCap: boolean, quantity?: number | null, billingProduct?: { __typename?: 'BillingProduct', name: string, description: string, metadata: { __typename?: 'BillingProductMetadata', planKey: BillingPlanKey, priceUsageBased: BillingUsageType, productKey: BillingProductKey } } | null }> | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, metadata: any }>, defaultRole?: { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, canAccessAllTools: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean } | null, defaultAgent?: { __typename?: 'Agent', id: any } | null } | null, availableWorkspaces: { __typename?: 'AvailableWorkspaces', availableWorkspacesForSignIn: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }>, availableWorkspacesForSignUp: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }> } } }; export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars?: any | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, deletedWorkspaceMembers?: Array<{ __typename?: 'DeletedWorkspaceMember', id: any, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', permissionFlags?: Array<PermissionFlagType> | null, objectRecordsPermissions?: Array<PermissionsOnAllObjectRecords> | null, objectPermissions?: Array<{ __typename?: 'ObjectPermission', objectMetadataId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null }> | null, twoFactorAuthenticationMethodSummary?: Array<{ __typename?: 'TwoFactorAuthenticationMethodDTO', twoFactorAuthenticationMethodId: any, status: string, strategy: string }> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, customDomain?: string | null, isCustomDomainEnabled: boolean, metadataVersion: number, workspaceMembersCount?: number | null, isTwoFactorAuthenticationEnforced: boolean, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlagDTO', key: FeatureFlagKey, value: boolean }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null, metadata: any, billingSubscriptionItems?: Array<{ __typename?: 'BillingSubscriptionItem', id: any, hasReachedCurrentPeriodCap: boolean, quantity?: number | null, billingProduct?: { __typename?: 'BillingProduct', name: string, description: string, metadata: { __typename?: 'BillingProductMetadata', planKey: BillingPlanKey, priceUsageBased: BillingUsageType, productKey: BillingProductKey } } | null }> | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, metadata: any }>, defaultRole?: { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, canAccessAllTools: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean } | null, defaultAgent?: { __typename?: 'Agent', id: any } | null } | null, availableWorkspaces: { __typename?: 'AvailableWorkspaces', availableWorkspacesForSignIn: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }>, availableWorkspacesForSignUp: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }> } } };
export type ActivateWorkflowVersionMutationVariables = Exact<{ export type ActivateWorkflowVersionMutationVariables = Exact<{
workflowVersionId: Scalars['String']; workflowVersionId: Scalars['String'];
@ -4196,7 +4198,7 @@ export const UserQueryFragmentFragmentDoc = gql`
...DeletedWorkspaceMemberQueryFragment ...DeletedWorkspaceMemberQueryFragment
} }
currentUserWorkspace { currentUserWorkspace {
settingsPermissions permissionFlags
objectRecordsPermissions objectRecordsPermissions
objectPermissions { objectPermissions {
...ObjectPermissionFragment ...ObjectPermissionFragment

View File

@ -1799,6 +1799,8 @@ export enum PermissionFlagType {
ADMIN_PANEL = 'ADMIN_PANEL', ADMIN_PANEL = 'ADMIN_PANEL',
API_KEYS_AND_WEBHOOKS = 'API_KEYS_AND_WEBHOOKS', API_KEYS_AND_WEBHOOKS = 'API_KEYS_AND_WEBHOOKS',
DATA_MODEL = 'DATA_MODEL', DATA_MODEL = 'DATA_MODEL',
EXPORT_CSV = 'EXPORT_CSV',
IMPORT_CSV = 'IMPORT_CSV',
ROLES = 'ROLES', ROLES = 'ROLES',
SECURITY = 'SECURITY', SECURITY = 'SECURITY',
SEND_EMAIL_TOOL = 'SEND_EMAIL_TOOL', SEND_EMAIL_TOOL = 'SEND_EMAIL_TOOL',
@ -2664,7 +2666,7 @@ export type UserWorkspace = {
objectPermissions?: Maybe<Array<ObjectPermission>>; objectPermissions?: Maybe<Array<ObjectPermission>>;
/** @deprecated Use objectPermissions instead */ /** @deprecated Use objectPermissions instead */
objectRecordsPermissions?: Maybe<Array<PermissionsOnAllObjectRecords>>; objectRecordsPermissions?: Maybe<Array<PermissionsOnAllObjectRecords>>;
settingsPermissions?: Maybe<Array<PermissionFlagType>>; permissionFlags?: Maybe<Array<PermissionFlagType>>;
twoFactorAuthenticationMethodSummary?: Maybe<Array<TwoFactorAuthenticationMethodDto>>; twoFactorAuthenticationMethodSummary?: Maybe<Array<TwoFactorAuthenticationMethodDto>>;
updatedAt: Scalars['DateTime']; updatedAt: Scalars['DateTime'];
user: User; user: User;

View File

@ -53,6 +53,7 @@ import {
IconTrashX, IconTrashX,
IconUser, IconUser,
} from 'twenty-ui/display'; } from 'twenty-ui/display';
import { PermissionFlagType } from '~/generated-metadata/graphql';
export const DEFAULT_RECORD_ACTIONS_CONFIG: Record< export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
| NoSelectionRecordActionKeys | NoSelectionRecordActionKeys
@ -169,6 +170,7 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
isDefined(selectedRecord) && !selectedRecord.isRemote, isDefined(selectedRecord) && !selectedRecord.isRemote,
availableOn: [ActionViewType.SHOW_PAGE], availableOn: [ActionViewType.SHOW_PAGE],
component: <ExportSingleRecordAction />, component: <ExportSingleRecordAction />,
requiredPermissionFlag: PermissionFlagType.EXPORT_CSV,
}, },
[MultipleRecordsActionKeys.EXPORT]: { [MultipleRecordsActionKeys.EXPORT]: {
type: ActionType.Standard, type: ActionType.Standard,
@ -183,6 +185,7 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
shouldBeRegistered: () => true, shouldBeRegistered: () => true,
availableOn: [ActionViewType.INDEX_PAGE_BULK_SELECTION], availableOn: [ActionViewType.INDEX_PAGE_BULK_SELECTION],
component: <ExportMultipleRecordsAction />, component: <ExportMultipleRecordsAction />,
requiredPermissionFlag: PermissionFlagType.EXPORT_CSV,
}, },
[NoSelectionRecordActionKeys.IMPORT_RECORDS]: { [NoSelectionRecordActionKeys.IMPORT_RECORDS]: {
type: ActionType.Standard, type: ActionType.Standard,
@ -198,6 +201,7 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
!isSoftDeleteFilterActive, !isSoftDeleteFilterActive,
availableOn: [ActionViewType.INDEX_PAGE_NO_SELECTION], availableOn: [ActionViewType.INDEX_PAGE_NO_SELECTION],
component: <ImportRecordsNoSelectionRecordAction />, component: <ImportRecordsNoSelectionRecordAction />,
requiredPermissionFlag: PermissionFlagType.IMPORT_CSV,
}, },
[NoSelectionRecordActionKeys.EXPORT_VIEW]: { [NoSelectionRecordActionKeys.EXPORT_VIEW]: {
type: ActionType.Standard, type: ActionType.Standard,
@ -212,6 +216,7 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
shouldBeRegistered: () => true, shouldBeRegistered: () => true,
availableOn: [ActionViewType.INDEX_PAGE_NO_SELECTION], availableOn: [ActionViewType.INDEX_PAGE_NO_SELECTION],
component: <ExportMultipleRecordsAction />, component: <ExportMultipleRecordsAction />,
requiredPermissionFlag: PermissionFlagType.EXPORT_CSV,
}, },
[SingleRecordActionKeys.DELETE]: { [SingleRecordActionKeys.DELETE]: {
type: ActionType.Standard, type: ActionType.Standard,

View File

@ -5,6 +5,7 @@ import { ShouldBeRegisteredFunctionParams } from '@/action-menu/actions/types/Sh
import { MessageDescriptor } from '@lingui/core'; import { MessageDescriptor } from '@lingui/core';
import { IconComponent } from 'twenty-ui/display'; import { IconComponent } from 'twenty-ui/display';
import { MenuItemAccent } from 'twenty-ui/navigation'; import { MenuItemAccent } from 'twenty-ui/navigation';
import { PermissionFlagType } from '~/generated-metadata/graphql';
export type ActionConfig = { export type ActionConfig = {
type: ActionType; type: ActionType;
@ -21,4 +22,5 @@ export type ActionConfig = {
shouldBeRegistered: (params: ShouldBeRegisteredFunctionParams) => boolean; shouldBeRegistered: (params: ShouldBeRegisteredFunctionParams) => boolean;
component: React.ReactNode; component: React.ReactNode;
hotKeys?: string[]; hotKeys?: string[];
requiredPermissionFlag?: PermissionFlagType;
}; };

View File

@ -6,6 +6,7 @@ import { getActionConfig } from '@/action-menu/actions/utils/getActionConfig';
import { getActionViewType } from '@/action-menu/actions/utils/getActionViewType'; import { getActionViewType } from '@/action-menu/actions/utils/getActionViewType';
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState'; import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
import { useIcons } from 'twenty-ui/display'; import { useIcons } from 'twenty-ui/display';
@ -48,6 +49,8 @@ export const useRegisteredActions = (
...recordAgnosticActionConfig, ...recordAgnosticActionConfig,
}; };
const permissionMap = usePermissionFlagMap();
const actionsToRegister = isDefined(viewType) const actionsToRegister = isDefined(viewType)
? Object.values(actionsConfig).filter( ? Object.values(actionsConfig).filter(
(action) => (action) =>
@ -57,7 +60,15 @@ export const useRegisteredActions = (
: []; : [];
const actions = actionsToRegister const actions = actionsToRegister
.filter((action) => action.shouldBeRegistered(shouldBeRegisteredParams)) .filter((action) => {
if (
isDefined(action.requiredPermissionFlag) &&
!permissionMap[action.requiredPermissionFlag]
) {
return false;
}
return action.shouldBeRegistered(shouldBeRegisteredParams);
})
.sort((a, b) => a.position - b.position); .sort((a, b) => a.position - b.position);
return actions; return actions;

View File

@ -3,7 +3,7 @@ import { UserWorkspace } from '~/generated/graphql';
export type CurrentUserWorkspace = Pick< export type CurrentUserWorkspace = Pick<
UserWorkspace, UserWorkspace,
| 'settingsPermissions' | 'permissionFlags'
| 'objectRecordsPermissions' | 'objectRecordsPermissions'
| 'objectPermissions' | 'objectPermissions'
| 'twoFactorAuthenticationMethodSummary' | 'twoFactorAuthenticationMethodSummary'

View File

@ -1,6 +1,6 @@
import { useRedirect } from '@/domain-manager/hooks/useRedirect'; import { useRedirect } from '@/domain-manager/hooks/useRedirect';
import { InformationBanner } from '@/information-banner/components/InformationBanner'; import { InformationBanner } from '@/information-banner/components/InformationBanner';
import { useSettingsPermissionMap } from '@/settings/roles/hooks/useSettingsPermissionMap'; import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap';
import { SettingsPath } from '@/types/SettingsPath'; import { SettingsPath } from '@/types/SettingsPath';
import { t } from '@lingui/core/macro'; import { t } from '@lingui/core/macro';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
@ -21,7 +21,7 @@ export const InformationBannerBillingSubscriptionPaused = () => {
const { const {
[PermissionFlagType.WORKSPACE]: hasPermissionToUpdateBillingDetails, [PermissionFlagType.WORKSPACE]: hasPermissionToUpdateBillingDetails,
} = useSettingsPermissionMap(); } = usePermissionFlagMap();
const openBillingPortal = () => { const openBillingPortal = () => {
if (isDefined(data) && isDefined(data.billingPortalSession.url)) { if (isDefined(data) && isDefined(data.billingPortalSession.url)) {

View File

@ -1,6 +1,6 @@
import { useEndSubscriptionTrialPeriod } from '@/billing/hooks/useEndSubscriptionTrialPeriod'; import { useEndSubscriptionTrialPeriod } from '@/billing/hooks/useEndSubscriptionTrialPeriod';
import { InformationBanner } from '@/information-banner/components/InformationBanner'; import { InformationBanner } from '@/information-banner/components/InformationBanner';
import { useSettingsPermissionMap } from '@/settings/roles/hooks/useSettingsPermissionMap'; import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap';
import { useLingui } from '@lingui/react/macro'; import { useLingui } from '@lingui/react/macro';
import { PermissionFlagType } from '~/generated-metadata/graphql'; import { PermissionFlagType } from '~/generated-metadata/graphql';
@ -9,7 +9,7 @@ export const InformationBannerEndTrialPeriod = () => {
const { t } = useLingui(); const { t } = useLingui();
const { [PermissionFlagType.WORKSPACE]: hasPermissionToEndTrialPeriod } = const { [PermissionFlagType.WORKSPACE]: hasPermissionToEndTrialPeriod } =
useSettingsPermissionMap(); usePermissionFlagMap();
return ( return (
<InformationBanner <InformationBanner

View File

@ -1,6 +1,6 @@
import { useRedirect } from '@/domain-manager/hooks/useRedirect'; import { useRedirect } from '@/domain-manager/hooks/useRedirect';
import { InformationBanner } from '@/information-banner/components/InformationBanner'; import { InformationBanner } from '@/information-banner/components/InformationBanner';
import { useSettingsPermissionMap } from '@/settings/roles/hooks/useSettingsPermissionMap'; import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap';
import { SettingsPath } from '@/types/SettingsPath'; import { SettingsPath } from '@/types/SettingsPath';
import { t } from '@lingui/core/macro'; import { t } from '@lingui/core/macro';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
@ -21,7 +21,7 @@ export const InformationBannerFailPaymentInfo = () => {
const { const {
[PermissionFlagType.WORKSPACE]: hasPermissionToUpdateBillingDetails, [PermissionFlagType.WORKSPACE]: hasPermissionToUpdateBillingDetails,
} = useSettingsPermissionMap(); } = usePermissionFlagMap();
const openBillingPortal = () => { const openBillingPortal = () => {
if (isDefined(data) && isDefined(data.billingPortalSession.url)) { if (isDefined(data) && isDefined(data.billingPortalSession.url)) {

View File

@ -1,7 +1,7 @@
import { BILLING_CHECKOUT_SESSION_DEFAULT_VALUE } from '@/billing/constants/BillingCheckoutSessionDefaultValue'; import { BILLING_CHECKOUT_SESSION_DEFAULT_VALUE } from '@/billing/constants/BillingCheckoutSessionDefaultValue';
import { useHandleCheckoutSession } from '@/billing/hooks/useHandleCheckoutSession'; import { useHandleCheckoutSession } from '@/billing/hooks/useHandleCheckoutSession';
import { InformationBanner } from '@/information-banner/components/InformationBanner'; import { InformationBanner } from '@/information-banner/components/InformationBanner';
import { useSettingsPermissionMap } from '@/settings/roles/hooks/useSettingsPermissionMap'; import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap';
import { SettingsPath } from '@/types/SettingsPath'; import { SettingsPath } from '@/types/SettingsPath';
import { t } from '@lingui/core/macro'; import { t } from '@lingui/core/macro';
import { PermissionFlagType } from '~/generated-metadata/graphql'; import { PermissionFlagType } from '~/generated-metadata/graphql';
@ -16,7 +16,7 @@ export const InformationBannerNoBillingSubscription = () => {
}); });
const { [PermissionFlagType.WORKSPACE]: hasPermissionToSubscribe } = const { [PermissionFlagType.WORKSPACE]: hasPermissionToSubscribe } =
useSettingsPermissionMap(); usePermissionFlagMap();
return ( return (
<InformationBanner <InformationBanner

View File

@ -132,7 +132,7 @@ export const queries = {
...WorkspaceMemberQueryFragment ...WorkspaceMemberQueryFragment
} }
currentUserWorkspace { currentUserWorkspace {
settingsPermissions permissionFlags
objectRecordsPermissions objectRecordsPermissions
} }
currentWorkspace { currentWorkspace {
@ -287,7 +287,7 @@ export const responseData = {
}, },
workspaceMembers: [], workspaceMembers: [],
currentUserWorkspace: { currentUserWorkspace: {
settingsPermissions: ['DATA_MODEL'], permissionFlags: ['DATA_MODEL'],
objectRecordsPermissions: [ objectRecordsPermissions: [
PermissionsOnAllObjectRecords.READ_ALL_OBJECT_RECORDS, PermissionsOnAllObjectRecords.READ_ALL_OBJECT_RECORDS,
PermissionsOnAllObjectRecords.UPDATE_ALL_OBJECT_RECORDS, PermissionsOnAllObjectRecords.UPDATE_ALL_OBJECT_RECORDS,

View File

@ -6,7 +6,7 @@ import { recordGroupFieldMetadataComponentState } from '@/object-record/record-g
import { visibleRecordGroupIdsComponentFamilySelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentFamilySelector'; import { visibleRecordGroupIdsComponentFamilySelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentFamilySelector';
import { RecordGroupAction } from '@/object-record/record-group/types/RecordGroupActions'; import { RecordGroupAction } from '@/object-record/record-group/types/RecordGroupActions';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext'; import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { useHasSettingsPermission } from '@/settings/roles/hooks/useHasSettingsPermission'; import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { SettingsPath } from '@/types/SettingsPath'; import { SettingsPath } from '@/types/SettingsPath';
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState'; import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2'; import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
@ -90,7 +90,7 @@ export const useRecordGroupActions = ({
recordGroupFieldMetadata, recordGroupFieldMetadata,
]); ]);
const hasAccessToDataModelSettings = useHasSettingsPermission( const hasAccessToDataModelSettings = useHasPermissionFlag(
PermissionFlagType.DATA_MODEL, PermissionFlagType.DATA_MODEL,
); );
const currentIndex = visibleRecordGroupIds.findIndex( const currentIndex = visibleRecordGroupIds.findIndex(

View File

@ -1,4 +1,4 @@
import { useHasSettingsPermission } from '@/settings/roles/hooks/useHasSettingsPermission'; import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { SettingsPath } from '@/types/SettingsPath'; import { SettingsPath } from '@/types/SettingsPath';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { ReactNode } from 'react'; import { ReactNode } from 'react';
@ -17,7 +17,7 @@ export const SettingsProtectedRouteWrapper = ({
settingsPermission, settingsPermission,
requiredFeatureFlag, requiredFeatureFlag,
}: SettingsProtectedRouteWrapperProps) => { }: SettingsProtectedRouteWrapperProps) => {
const hasPermission = useHasSettingsPermission(settingsPermission); const hasPermission = useHasPermissionFlag(settingsPermission);
const requiredFeatureFlagEnabled = useIsFeatureEnabled( const requiredFeatureFlagEnabled = useIsFeatureEnabled(
requiredFeatureFlag || null, requiredFeatureFlag || null,
); );

View File

@ -13,7 +13,7 @@ import {
import { currentUserState } from '@/auth/states/currentUserState'; import { currentUserState } from '@/auth/states/currentUserState';
import { billingState } from '@/client-config/states/billingState'; import { billingState } from '@/client-config/states/billingState';
import { labPublicFeatureFlagsState } from '@/client-config/states/labPublicFeatureFlagsState'; import { labPublicFeatureFlagsState } from '@/client-config/states/labPublicFeatureFlagsState';
import { useSettingsPermissionMap } from '@/settings/roles/hooks/useSettingsPermissionMap'; import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap';
import { SnackBarComponentInstanceContextProvider } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarComponentInstanceContextProvider'; import { SnackBarComponentInstanceContextProvider } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarComponentInstanceContextProvider';
const mockCurrentUser = { const mockCurrentUser = {
@ -53,13 +53,13 @@ const Wrapper = ({ children }: { children: ReactNode }) => (
</MockedProvider> </MockedProvider>
); );
jest.mock('@/settings/roles/hooks/useSettingsPermissionMap', () => ({ jest.mock('@/settings/roles/hooks/usePermissionFlagMap', () => ({
useSettingsPermissionMap: jest.fn(), usePermissionFlagMap: jest.fn(),
})); }));
describe('useSettingsNavigationItems', () => { describe('useSettingsNavigationItems', () => {
it('should hide workspace settings when no permissions', () => { it('should hide workspace settings when no permissions', () => {
(useSettingsPermissionMap as jest.Mock).mockImplementation(() => ({ (usePermissionFlagMap as jest.Mock).mockImplementation(() => ({
[PermissionFlagType.WORKSPACE]: false, [PermissionFlagType.WORKSPACE]: false,
[PermissionFlagType.WORKSPACE_MEMBERS]: false, [PermissionFlagType.WORKSPACE_MEMBERS]: false,
[PermissionFlagType.DATA_MODEL]: false, [PermissionFlagType.DATA_MODEL]: false,
@ -80,7 +80,7 @@ describe('useSettingsNavigationItems', () => {
}); });
it('should show workspace settings when has permissions', () => { it('should show workspace settings when has permissions', () => {
(useSettingsPermissionMap as jest.Mock).mockImplementation(() => ({ (usePermissionFlagMap as jest.Mock).mockImplementation(() => ({
[PermissionFlagType.WORKSPACE]: true, [PermissionFlagType.WORKSPACE]: true,
[PermissionFlagType.WORKSPACE_MEMBERS]: true, [PermissionFlagType.WORKSPACE_MEMBERS]: true,
[PermissionFlagType.DATA_MODEL]: true, [PermissionFlagType.DATA_MODEL]: true,

View File

@ -4,7 +4,7 @@ import { useAuth } from '@/auth/hooks/useAuth';
import { currentUserState } from '@/auth/states/currentUserState'; import { currentUserState } from '@/auth/states/currentUserState';
import { billingState } from '@/client-config/states/billingState'; import { billingState } from '@/client-config/states/billingState';
import { labPublicFeatureFlagsState } from '@/client-config/states/labPublicFeatureFlagsState'; import { labPublicFeatureFlagsState } from '@/client-config/states/labPublicFeatureFlagsState';
import { useSettingsPermissionMap } from '@/settings/roles/hooks/useSettingsPermissionMap'; import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap';
import { NavigationDrawerItemIndentationLevel } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem'; import { NavigationDrawerItemIndentationLevel } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
import { t } from '@lingui/core/macro'; import { t } from '@lingui/core/macro';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
@ -63,7 +63,7 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
false; false;
const labPublicFeatureFlags = useRecoilValue(labPublicFeatureFlagsState); const labPublicFeatureFlags = useRecoilValue(labPublicFeatureFlagsState);
const permissionMap = useSettingsPermissionMap(); const permissionMap = usePermissionFlagMap();
return [ return [
{ {
label: t`User`, label: t`User`,

View File

@ -4,9 +4,7 @@ import { useRecoilValue } from 'recoil';
import { WorkspaceActivationStatus } from 'twenty-shared/workspace'; import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
import { PermissionFlagType } from '~/generated/graphql'; import { PermissionFlagType } from '~/generated/graphql';
export const useHasSettingsPermission = ( export const useHasPermissionFlag = (permissionFlag?: PermissionFlagType) => {
permissionFlag?: PermissionFlagType,
) => {
const currentWorkspace = useRecoilValue(currentWorkspaceState); const currentWorkspace = useRecoilValue(currentWorkspaceState);
const currentUserWorkspace = useRecoilValue(currentUserWorkspaceState); const currentUserWorkspace = useRecoilValue(currentUserWorkspaceState);
@ -22,7 +20,7 @@ export const useHasSettingsPermission = (
return true; return true;
} }
const currentUserWorkspaceSetting = currentUserWorkspace?.settingsPermissions; const currentUserWorkspaceSetting = currentUserWorkspace?.permissionFlags;
if (!currentUserWorkspaceSetting) { if (!currentUserWorkspaceSetting) {
return false; return false;

View File

@ -3,14 +3,11 @@ import { useRecoilValue } from 'recoil';
import { PermissionFlagType } from '~/generated/graphql'; import { PermissionFlagType } from '~/generated/graphql';
import { buildRecordFromKeysWithSameValue } from '~/utils/array/buildRecordFromKeysWithSameValue'; import { buildRecordFromKeysWithSameValue } from '~/utils/array/buildRecordFromKeysWithSameValue';
export const useSettingsPermissionMap = (): Record< export const usePermissionFlagMap = (): Record<PermissionFlagType, boolean> => {
PermissionFlagType,
boolean
> => {
const currentUserWorkspace = useRecoilValue(currentUserWorkspaceState); const currentUserWorkspace = useRecoilValue(currentUserWorkspaceState);
const currentUserWorkspaceSettingsPermissions = const currentUserWorkspaceSettingsPermissions =
currentUserWorkspace?.settingsPermissions; currentUserWorkspace?.permissionFlags;
const initialPermissions = buildRecordFromKeysWithSameValue( const initialPermissions = buildRecordFromKeysWithSameValue(
Object.values(PermissionFlagType), Object.values(PermissionFlagType),

View File

@ -7,7 +7,13 @@ import styled from '@emotion/styled';
import { t } from '@lingui/core/macro'; import { t } from '@lingui/core/macro';
import { useRecoilState } from 'recoil'; import { useRecoilState } from 'recoil';
import { H2Title, IconMail, IconTool } from 'twenty-ui/display'; import {
H2Title,
IconFileExport,
IconFileImport,
IconMail,
IconTool,
} from 'twenty-ui/display';
import { AnimatedExpandableContainer, Card, Section } from 'twenty-ui/layout'; import { AnimatedExpandableContainer, Card, Section } from 'twenty-ui/layout';
import { PermissionFlagType } from '~/generated-metadata/graphql'; import { PermissionFlagType } from '~/generated-metadata/graphql';
@ -45,19 +51,30 @@ export const SettingsRolePermissionsToolSection = ({
Icon: IconMail, Icon: IconMail,
isToolPermission: true, isToolPermission: true,
}, },
{
key: PermissionFlagType.IMPORT_CSV,
name: t`Import CSV`,
description: t`Allow importing data from CSV files`,
Icon: IconFileImport,
isToolPermission: true,
},
{
key: PermissionFlagType.EXPORT_CSV,
name: t`Export CSV`,
description: t`Allow exporting data to CSV files`,
Icon: IconFileExport,
isToolPermission: true,
},
]; ];
return ( return (
<Section> <Section>
<H2Title <H2Title title={t`Actions`} description={t`Actions permissions`} />
title={t`Action Permissions`}
description={t`Permissions for performing automated actions.`}
/>
<StyledCard rounded> <StyledCard rounded>
<SettingsOptionCardContentToggle <SettingsOptionCardContentToggle
Icon={IconTool} Icon={IconTool}
title={t`All Actions Access`} title={t`All Actions Access`}
description={t`Grants permission to perform all available actions without restriction.`} description={t`Grants permission to perform all available actions without restriction`}
checked={settingsDraftRole.canAccessAllTools} checked={settingsDraftRole.canAccessAllTools}
disabled={!isEditable} disabled={!isEditable}
onChange={() => { onChange={() => {

View File

@ -34,7 +34,7 @@ export const USER_QUERY_FRAGMENT = gql`
...DeletedWorkspaceMemberQueryFragment ...DeletedWorkspaceMemberQueryFragment
} }
currentUserWorkspace { currentUserWorkspace {
settingsPermissions permissionFlags
objectRecordsPermissions objectRecordsPermissions
objectPermissions { objectPermissions {
...ObjectPermissionFragment ...ObjectPermissionFragment

View File

@ -125,7 +125,7 @@ export const mockedUserData: MockedUser = {
workspaceMember: mockedWorkspaceMemberData, workspaceMember: mockedWorkspaceMemberData,
currentWorkspace: mockCurrentWorkspace, currentWorkspace: mockCurrentWorkspace,
currentUserWorkspace: { currentUserWorkspace: {
settingsPermissions: [PermissionFlagType.WORKSPACE_MEMBERS], permissionFlags: [PermissionFlagType.WORKSPACE_MEMBERS],
objectPermissions: generatedMockObjectMetadataItems.map((item) => ({ objectPermissions: generatedMockObjectMetadataItems.map((item) => ({
objectMetadataId: item.id, objectMetadataId: item.id,
canReadObjectRecords: true, canReadObjectRecords: true,

View File

@ -97,7 +97,7 @@ export class UserWorkspace {
twoFactorAuthenticationMethods: Relation<TwoFactorAuthenticationMethod[]>; twoFactorAuthenticationMethods: Relation<TwoFactorAuthenticationMethod[]>;
@Field(() => [PermissionFlagType], { nullable: true }) @Field(() => [PermissionFlagType], { nullable: true })
settingsPermissions?: PermissionFlagType[]; permissionFlags?: PermissionFlagType[];
@Field(() => [PermissionsOnAllObjectRecords], { @Field(() => [PermissionsOnAllObjectRecords], {
nullable: true, nullable: true,

View File

@ -11,4 +11,6 @@ export enum PermissionFlagType {
// Tool permissions // Tool permissions
SEND_EMAIL_TOOL = 'SEND_EMAIL_TOOL', SEND_EMAIL_TOOL = 'SEND_EMAIL_TOOL',
IMPORT_CSV = 'IMPORT_CSV',
EXPORT_CSV = 'EXPORT_CSV',
} }

View File

@ -0,0 +1,5 @@
export const TOOL_PERMISSION_FLAGS = [
'SEND_EMAIL_TOOL',
'IMPORT_CSV',
'EXPORT_CSV',
];

View File

@ -10,6 +10,7 @@ import {
AuthExceptionCode, AuthExceptionCode,
} from 'src/engine/core-modules/auth/auth.exception'; } from 'src/engine/core-modules/auth/auth.exception';
import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants'; import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants';
import { TOOL_PERMISSION_FLAGS } from 'src/engine/metadata-modules/permissions/constants/tool-permission-flags';
import { import {
PermissionsException, PermissionsException,
PermissionsExceptionCode, PermissionsExceptionCode,
@ -29,6 +30,10 @@ export class PermissionsService {
private readonly roleRepository: Repository<RoleEntity>, private readonly roleRepository: Repository<RoleEntity>,
) {} ) {}
private isToolPermission(feature: string) {
return TOOL_PERMISSION_FLAGS.includes(feature);
}
public async getUserWorkspacePermissions({ public async getUserWorkspacePermissions({
userWorkspaceId, userWorkspaceId,
workspaceId, workspaceId,
@ -43,8 +48,6 @@ export class PermissionsService {
}) })
.then((roles) => roles?.get(userWorkspaceId) ?? []); .then((roles) => roles?.get(userWorkspaceId) ?? []);
let hasPermissionOnSettingFeature = false;
if (!isDefined(roleOfUserWorkspace)) { if (!isDefined(roleOfUserWorkspace)) {
throw new PermissionsException( throw new PermissionsException(
PermissionsExceptionMessage.NO_ROLE_FOUND_FOR_USER_WORKSPACE, PermissionsExceptionMessage.NO_ROLE_FOUND_FOR_USER_WORKSPACE,
@ -52,23 +55,23 @@ export class PermissionsService {
); );
} }
if (roleOfUserWorkspace.canUpdateAllSettings === true) {
hasPermissionOnSettingFeature = true;
}
const permissionFlags = roleOfUserWorkspace.permissionFlags ?? [];
const defaultSettingsPermissions = const defaultSettingsPermissions =
this.getDefaultUserWorkspacePermissions().settingsPermissions; this.getDefaultUserWorkspacePermissions().permissionFlags;
const settingsPermissions = Object.keys(PermissionFlagType).reduce( const permissionFlags = Object.keys(PermissionFlagType).reduce(
(acc, feature) => ({ (acc, feature) => {
...acc, const hasBasePermission = this.isToolPermission(feature)
[feature]: ? roleOfUserWorkspace.canAccessAllTools
hasPermissionOnSettingFeature || : roleOfUserWorkspace.canUpdateAllSettings;
permissionFlags.some(
(permissionFlag) => permissionFlag.flag === feature, return {
), ...acc,
}), [feature]:
hasBasePermission ||
roleOfUserWorkspace.permissionFlags.some(
(permissionFlag) => permissionFlag.flag === feature,
),
};
},
defaultSettingsPermissions, defaultSettingsPermissions,
); );
@ -92,7 +95,7 @@ export class PermissionsService {
}; };
return { return {
settingsPermissions, permissionFlags,
objectRecordsPermissions, objectRecordsPermissions,
objectPermissions, objectPermissions,
}; };
@ -106,7 +109,7 @@ export class PermissionsService {
[PermissionsOnAllObjectRecords.SOFT_DELETE_ALL_OBJECT_RECORDS]: false, [PermissionsOnAllObjectRecords.SOFT_DELETE_ALL_OBJECT_RECORDS]: false,
[PermissionsOnAllObjectRecords.DESTROY_ALL_OBJECT_RECORDS]: false, [PermissionsOnAllObjectRecords.DESTROY_ALL_OBJECT_RECORDS]: false,
}, },
settingsPermissions: { permissionFlags: {
[PermissionFlagType.API_KEYS_AND_WEBHOOKS]: false, [PermissionFlagType.API_KEYS_AND_WEBHOOKS]: false,
[PermissionFlagType.WORKSPACE]: false, [PermissionFlagType.WORKSPACE]: false,
[PermissionFlagType.WORKSPACE_MEMBERS]: false, [PermissionFlagType.WORKSPACE_MEMBERS]: false,
@ -116,6 +119,8 @@ export class PermissionsService {
[PermissionFlagType.SECURITY]: false, [PermissionFlagType.SECURITY]: false,
[PermissionFlagType.WORKFLOWS]: false, [PermissionFlagType.WORKFLOWS]: false,
[PermissionFlagType.SEND_EMAIL_TOOL]: false, [PermissionFlagType.SEND_EMAIL_TOOL]: false,
[PermissionFlagType.IMPORT_CSV]: false,
[PermissionFlagType.EXPORT_CSV]: false,
}, },
objectPermissions: {}, objectPermissions: {},
}) as const satisfies UserWorkspacePermissions; }) as const satisfies UserWorkspacePermissions;

View File

@ -4,7 +4,7 @@ import { ObjectRecordsPermissions } from 'twenty-shared/types';
import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants'; import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants';
export type UserWorkspacePermissions = { export type UserWorkspacePermissions = {
settingsPermissions: Record<PermissionFlagType, boolean>; permissionFlags: Record<PermissionFlagType, boolean>;
objectRecordsPermissions: Record<PermissionsOnAllObjectRecords, boolean>; objectRecordsPermissions: Record<PermissionsOnAllObjectRecords, boolean>;
objectPermissions: ObjectRecordsPermissions; objectPermissions: ObjectRecordsPermissions;
}; };

View File

@ -2,5 +2,5 @@ import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-works
export type UserWorkspacePermissionsDto = Pick< export type UserWorkspacePermissionsDto = Pick<
UserWorkspace, UserWorkspace,
'objectPermissions' | 'settingsPermissions' | 'objectRecordsPermissions' 'objectPermissions' | 'permissionFlags' | 'objectRecordsPermissions'
>; >;

View File

@ -7,7 +7,7 @@ import { UserWorkspacePermissionsDto } from 'src/engine/metadata-modules/role/dt
export const fromUserWorkspacePermissionsToUserWorkspacePermissionsDto = ({ export const fromUserWorkspacePermissionsToUserWorkspacePermissionsDto = ({
objectPermissions: rawObjectPermissions, objectPermissions: rawObjectPermissions,
objectRecordsPermissions: rawObjectRecordsPermissions, objectRecordsPermissions: rawObjectRecordsPermissions,
settingsPermissions: rawSettingsPermissions, permissionFlags: rawSettingsPermissions,
}: UserWorkspacePermissions): UserWorkspacePermissionsDto => { }: UserWorkspacePermissions): UserWorkspacePermissionsDto => {
const objectPermissions = Object.entries(rawObjectPermissions).map( const objectPermissions = Object.entries(rawObjectPermissions).map(
([objectMetadataId, permissions]) => ({ ([objectMetadataId, permissions]) => ({
@ -19,7 +19,7 @@ export const fromUserWorkspacePermissionsToUserWorkspacePermissionsDto = ({
}), }),
); );
const settingsPermissions = ( const permissionFlags = (
Object.keys(rawSettingsPermissions) as PermissionFlagType[] Object.keys(rawSettingsPermissions) as PermissionFlagType[]
).filter((feature) => rawSettingsPermissions[feature] === true); ).filter((feature) => rawSettingsPermissions[feature] === true);
@ -30,6 +30,6 @@ export const fromUserWorkspacePermissionsToUserWorkspacePermissionsDto = ({
return { return {
objectPermissions, objectPermissions,
objectRecordsPermissions, objectRecordsPermissions,
settingsPermissions, permissionFlags,
}; };
}; };

View File

@ -110,6 +110,7 @@ export class DevSeederPermissionsService {
'All permissions except read on Rockets and update on Pets', 'All permissions except read on Rockets and update on Pets',
icon: 'custom', icon: 'custom',
canUpdateAllSettings: true, canUpdateAllSettings: true,
canAccessAllTools: true,
canReadAllObjectRecords: true, canReadAllObjectRecords: true,
canUpdateAllObjectRecords: true, canUpdateAllObjectRecords: true,
canSoftDeleteAllObjectRecords: true, canSoftDeleteAllObjectRecords: true,