Compare commits

...
Author SHA1 Message Date
ehconitin fe16b86190 Cherry-pick from permission-flag-definition-foundation: add resolver and integration tests for permissionFlag catalog 2026-05-12 16:35:52 +05:30
ehconitin fb30c88742 Regenerate metadata GraphQL files after merging upstream/main 2026-05-12 16:31:21 +05:30
ehconitin e798fe0314 Merge remote-tracking branch 'upstream/main' into rename-permission-flag-table
# Conflicts:
#	packages/twenty-client-sdk/src/metadata/generated/schema.ts
#	packages/twenty-front/src/generated-admin/graphql.ts
#	packages/twenty-front/src/generated-metadata/graphql.ts
2026-05-12 16:20:48 +05:30
ehconitin f5b45f3641 Cherry-pick from permission-flag-definition-foundation: rename permissionFlag to permissionFlagGrant and add catalog entity (no API yet) 2026-05-12 15:37:40 +05:30
232 changed files with 4363 additions and 1035 deletions
@@ -1,4 +1,4 @@
import { PermissionFlag, defineRole } from 'twenty-sdk/define';
import { PermissionFlagGrant, defineRole } from 'twenty-sdk/define';
import {
CONTENT_FIELD_UNIVERSAL_IDENTIFIER,
POST_CARD_UNIVERSAL_IDENTIFIER,
@@ -36,5 +36,5 @@ export default defineRole({
canUpdateFieldValue: true,
},
],
permissionFlags: [PermissionFlag.APPLICATIONS],
permissionFlagGrants: [PermissionFlagGrant.APPLICATIONS],
});
@@ -1,4 +1,4 @@
import { PermissionFlag, defineRole } from 'twenty-sdk/define';
import { PermissionFlagGrant, defineRole } from 'twenty-sdk/define';
import {
CONTENT_FIELD_UNIVERSAL_IDENTIFIER,
POST_CARD_UNIVERSAL_IDENTIFIER,
@@ -36,5 +36,5 @@ export default defineRole({
canUpdateFieldValue: false,
},
],
permissionFlags: [PermissionFlag.APPLICATIONS],
permissionFlagGrants: [PermissionFlagGrant.APPLICATIONS],
});
@@ -12,5 +12,5 @@ export default defineRole({
canUpdateAllObjectRecords: true,
canSoftDeleteAllObjectRecords: true,
canDestroyAllObjectRecords: false,
permissionFlags: [PermissionFlagType.UPLOAD_FILE, PermissionFlagType.AI],
permissionFlagGrants: [PermissionFlagType.UPLOAD_FILE, PermissionFlagType.AI],
});
@@ -21,5 +21,5 @@ export default defineRole({
canBeAssignedToApiKeys: false,
objectPermissions: [],
fieldPermissions: [],
permissionFlags: [],
permissionFlagGrants: [],
});
@@ -18,5 +18,5 @@ export default defineRole({
canBeAssignedToApiKeys: false,
objectPermissions: [],
fieldPermissions: [],
permissionFlags: [],
permissionFlagGrants: [],
});
@@ -138,7 +138,7 @@ type UserWorkspace {
createdAt: DateTime!
updatedAt: DateTime!
deletedAt: DateTime
permissionFlags: [PermissionFlagType!]
permissionFlagGrants: [PermissionFlagType!]
objectPermissions: [ObjectPermission!]
objectsPermissions: [ObjectPermission!]
twoFactorAuthenticationMethodSummary: [TwoFactorAuthenticationMethodSummary!]
@@ -244,7 +244,7 @@ type FieldPermission {
canUpdateFieldValue: Boolean
}
type PermissionFlag {
type PermissionFlagGrant {
id: UUID!
roleId: UUID!
flag: PermissionFlagType!
@@ -276,7 +276,7 @@ type Role {
canUpdateAllObjectRecords: Boolean!
canSoftDeleteAllObjectRecords: Boolean!
canDestroyAllObjectRecords: Boolean!
permissionFlags: [PermissionFlag!]
permissionFlagGrants: [PermissionFlagGrant!]
objectPermissions: [ObjectPermission!]
fieldPermissions: [FieldPermission!]
rowLevelPermissionPredicates: [RowLevelPermissionPredicate!]
@@ -1756,6 +1756,7 @@ enum FeatureFlagKey {
IS_JUNCTION_RELATIONS_ENABLED
IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED
IS_BILLING_V2_ENABLED
IS_CUSTOM_PERMISSION_FLAGS_ENABLED
}
type WorkspaceUrls {
@@ -2879,6 +2880,7 @@ enum AllMetadataName {
pageLayoutTab
commandMenuItem
navigationMenuItem
permissionFlagGrant
permissionFlag
objectPermission
fieldPermission
@@ -3233,7 +3235,7 @@ type Mutation {
updateOneRole(updateRoleInput: UpdateRoleInput!): Role!
deleteOneRole(roleId: UUID!): String!
upsertObjectPermissions(upsertObjectPermissionsInput: UpsertObjectPermissionsInput!): [ObjectPermission!]!
upsertPermissionFlags(upsertPermissionFlagsInput: UpsertPermissionFlagsInput!): [PermissionFlag!]!
upsertPermissionFlagGrants(upsertPermissionFlagGrantsInput: UpsertPermissionFlagGrantsInput!): [PermissionFlagGrant!]!
upsertFieldPermissions(upsertFieldPermissionsInput: UpsertFieldPermissionsInput!): [FieldPermission!]!
upsertRowLevelPermissionPredicates(input: UpsertRowLevelPermissionPredicatesInput!): UpsertRowLevelPermissionPredicatesResult!
assignRoleToAgent(agentId: UUID!, roleId: UUID!): Boolean!
@@ -4016,9 +4018,9 @@ input ObjectPermissionInput {
canDestroyObjectRecords: Boolean
}
input UpsertPermissionFlagsInput {
input UpsertPermissionFlagGrantsInput {
roleId: UUID!
permissionFlagKeys: [PermissionFlagType!]!
permissionFlagGrantKeys: [PermissionFlagType!]!
}
input UpsertFieldPermissionsInput {
@@ -117,7 +117,7 @@ export interface UserWorkspace {
createdAt: Scalars['DateTime']
updatedAt: Scalars['DateTime']
deletedAt?: Scalars['DateTime']
permissionFlags?: PermissionFlagType[]
permissionFlagGrants?: PermissionFlagType[]
objectPermissions?: ObjectPermission[]
objectsPermissions?: ObjectPermission[]
twoFactorAuthenticationMethodSummary?: TwoFactorAuthenticationMethodSummary[]
@@ -190,11 +190,11 @@ export interface FieldPermission {
__typename: 'FieldPermission'
}
export interface PermissionFlag {
export interface PermissionFlagGrant {
id: Scalars['UUID']
roleId: Scalars['UUID']
flag: PermissionFlagType
__typename: 'PermissionFlag'
__typename: 'PermissionFlagGrant'
}
export interface ApiKeyForRole {
@@ -224,7 +224,7 @@ export interface Role {
canUpdateAllObjectRecords: Scalars['Boolean']
canSoftDeleteAllObjectRecords: Scalars['Boolean']
canDestroyAllObjectRecords: Scalars['Boolean']
permissionFlags?: PermissionFlag[]
permissionFlagGrants?: PermissionFlagGrant[]
objectPermissions?: ObjectPermission[]
fieldPermissions?: FieldPermission[]
rowLevelPermissionPredicates?: RowLevelPermissionPredicate[]
@@ -1389,7 +1389,7 @@ export interface FeatureFlag {
__typename: 'FeatureFlag'
}
export type FeatureFlagKey = 'IS_UNIQUE_INDEXES_ENABLED' | 'IS_JSON_FILTER_ENABLED' | 'IS_MARKETPLACE_SETTING_TAB_VISIBLE' | 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' | 'IS_PUBLIC_DOMAIN_ENABLED' | 'IS_EMAILING_DOMAIN_ENABLED' | 'IS_EMAIL_GROUP_ENABLED' | 'IS_JUNCTION_RELATIONS_ENABLED' | 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' | 'IS_BILLING_V2_ENABLED'
export type FeatureFlagKey = 'IS_UNIQUE_INDEXES_ENABLED' | 'IS_JSON_FILTER_ENABLED' | 'IS_MARKETPLACE_SETTING_TAB_VISIBLE' | 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' | 'IS_PUBLIC_DOMAIN_ENABLED' | 'IS_EMAILING_DOMAIN_ENABLED' | 'IS_EMAIL_GROUP_ENABLED' | 'IS_JUNCTION_RELATIONS_ENABLED' | 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' | 'IS_BILLING_V2_ENABLED' | 'IS_CUSTOM_PERMISSION_FLAGS_ENABLED'
export interface WorkspaceUrls {
customUrl?: Scalars['String']
@@ -2508,7 +2508,7 @@ export interface CollectionHash {
__typename: 'CollectionHash'
}
export type AllMetadataName = 'fieldMetadata' | 'objectMetadata' | 'view' | 'viewField' | 'viewFieldGroup' | 'viewGroup' | 'viewSort' | 'rowLevelPermissionPredicate' | 'rowLevelPermissionPredicateGroup' | 'viewFilterGroup' | 'index' | 'logicFunction' | 'viewFilter' | 'role' | 'roleTarget' | 'agent' | 'skill' | 'pageLayout' | 'pageLayoutWidget' | 'pageLayoutTab' | 'commandMenuItem' | 'navigationMenuItem' | 'permissionFlag' | 'objectPermission' | 'fieldPermission' | 'frontComponent' | 'webhook' | 'applicationVariable' | 'connectionProvider'
export type AllMetadataName = 'fieldMetadata' | 'objectMetadata' | 'view' | 'viewField' | 'viewFieldGroup' | 'viewGroup' | 'viewSort' | 'rowLevelPermissionPredicate' | 'rowLevelPermissionPredicateGroup' | 'viewFilterGroup' | 'index' | 'logicFunction' | 'viewFilter' | 'role' | 'roleTarget' | 'agent' | 'skill' | 'pageLayout' | 'pageLayoutWidget' | 'pageLayoutTab' | 'commandMenuItem' | 'navigationMenuItem' | 'permissionFlagGrant' | 'permissionFlag' | 'objectPermission' | 'fieldPermission' | 'frontComponent' | 'webhook' | 'applicationVariable' | 'connectionProvider'
export interface MinimalObjectMetadata {
id: Scalars['UUID']
@@ -2765,7 +2765,7 @@ export interface Mutation {
updateOneRole: Role
deleteOneRole: Scalars['String']
upsertObjectPermissions: ObjectPermission[]
upsertPermissionFlags: PermissionFlag[]
upsertPermissionFlagGrants: PermissionFlagGrant[]
upsertFieldPermissions: FieldPermission[]
upsertRowLevelPermissionPredicates: UpsertRowLevelPermissionPredicatesResult
assignRoleToAgent: Scalars['Boolean']
@@ -3003,7 +3003,7 @@ export interface UserWorkspaceGenqlSelection{
createdAt?: boolean | number
updatedAt?: boolean | number
deletedAt?: boolean | number
permissionFlags?: boolean | number
permissionFlagGrants?: boolean | number
objectPermissions?: ObjectPermissionGenqlSelection
objectsPermissions?: ObjectPermissionGenqlSelection
twoFactorAuthenticationMethodSummary?: TwoFactorAuthenticationMethodSummaryGenqlSelection
@@ -3067,7 +3067,7 @@ export interface FieldPermissionGenqlSelection{
__scalar?: boolean | number
}
export interface PermissionFlagGenqlSelection{
export interface PermissionFlagGrantGenqlSelection{
id?: boolean | number
roleId?: boolean | number
flag?: boolean | number
@@ -3103,7 +3103,7 @@ export interface RoleGenqlSelection{
canUpdateAllObjectRecords?: boolean | number
canSoftDeleteAllObjectRecords?: boolean | number
canDestroyAllObjectRecords?: boolean | number
permissionFlags?: PermissionFlagGenqlSelection
permissionFlagGrants?: PermissionFlagGrantGenqlSelection
objectPermissions?: ObjectPermissionGenqlSelection
fieldPermissions?: FieldPermissionGenqlSelection
rowLevelPermissionPredicates?: RowLevelPermissionPredicateGenqlSelection
@@ -5835,7 +5835,7 @@ export interface MutationGenqlSelection{
updateOneRole?: (RoleGenqlSelection & { __args: {updateRoleInput: UpdateRoleInput} })
deleteOneRole?: { __args: {roleId: Scalars['UUID']} }
upsertObjectPermissions?: (ObjectPermissionGenqlSelection & { __args: {upsertObjectPermissionsInput: UpsertObjectPermissionsInput} })
upsertPermissionFlags?: (PermissionFlagGenqlSelection & { __args: {upsertPermissionFlagsInput: UpsertPermissionFlagsInput} })
upsertPermissionFlagGrants?: (PermissionFlagGrantGenqlSelection & { __args: {upsertPermissionFlagGrantsInput: UpsertPermissionFlagGrantsInput} })
upsertFieldPermissions?: (FieldPermissionGenqlSelection & { __args: {upsertFieldPermissionsInput: UpsertFieldPermissionsInput} })
upsertRowLevelPermissionPredicates?: (UpsertRowLevelPermissionPredicatesResultGenqlSelection & { __args: {input: UpsertRowLevelPermissionPredicatesInput} })
assignRoleToAgent?: { __args: {agentId: Scalars['UUID'], roleId: Scalars['UUID']} }
@@ -6175,7 +6175,7 @@ export interface UpsertObjectPermissionsInput {roleId: Scalars['UUID'],objectPer
export interface ObjectPermissionInput {objectMetadataId: Scalars['UUID'],canReadObjectRecords?: (Scalars['Boolean'] | null),canUpdateObjectRecords?: (Scalars['Boolean'] | null),canSoftDeleteObjectRecords?: (Scalars['Boolean'] | null),canDestroyObjectRecords?: (Scalars['Boolean'] | null)}
export interface UpsertPermissionFlagsInput {roleId: Scalars['UUID'],permissionFlagKeys: PermissionFlagType[]}
export interface UpsertPermissionFlagGrantsInput {roleId: Scalars['UUID'],permissionFlagGrantKeys: PermissionFlagType[]}
export interface UpsertFieldPermissionsInput {roleId: Scalars['UUID'],fieldPermissions: FieldPermissionInput[]}
@@ -6416,10 +6416,10 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null
const PermissionFlag_possibleTypes: string[] = ['PermissionFlag']
export const isPermissionFlag = (obj?: { __typename?: any } | null): obj is PermissionFlag => {
if (!obj?.__typename) throw new Error('__typename is missing in "isPermissionFlag"')
return PermissionFlag_possibleTypes.includes(obj.__typename)
const PermissionFlagGrant_possibleTypes: string[] = ['PermissionFlagGrant']
export const isPermissionFlagGrant = (obj?: { __typename?: any } | null): obj is PermissionFlagGrant => {
if (!obj?.__typename) throw new Error('__typename is missing in "isPermissionFlagGrant"')
return PermissionFlagGrant_possibleTypes.includes(obj.__typename)
}
@@ -8730,7 +8730,8 @@ export const enumFeatureFlagKey = {
IS_EMAIL_GROUP_ENABLED: 'IS_EMAIL_GROUP_ENABLED' as const,
IS_JUNCTION_RELATIONS_ENABLED: 'IS_JUNCTION_RELATIONS_ENABLED' as const,
IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED: 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' as const,
IS_BILLING_V2_ENABLED: 'IS_BILLING_V2_ENABLED' as const
IS_BILLING_V2_ENABLED: 'IS_BILLING_V2_ENABLED' as const,
IS_CUSTOM_PERMISSION_FLAGS_ENABLED: 'IS_CUSTOM_PERMISSION_FLAGS_ENABLED' as const
}
export const enumIdentityProviderType = {
@@ -8892,6 +8893,7 @@ export const enumAllMetadataName = {
pageLayoutTab: 'pageLayoutTab' as const,
commandMenuItem: 'commandMenuItem' as const,
navigationMenuItem: 'navigationMenuItem' as const,
permissionFlagGrant: 'permissionFlagGrant' as const,
permissionFlag: 'permissionFlag' as const,
objectPermission: 'objectPermission' as const,
fieldPermission: 'fieldPermission' as const,
@@ -351,7 +351,7 @@ export default {
"deletedAt": [
4
],
"permissionFlags": [
"permissionFlagGrants": [
18
],
"objectPermissions": [
@@ -500,7 +500,7 @@ export default {
1
]
},
"PermissionFlag": {
"PermissionFlagGrant": {
"id": [
3
],
@@ -586,7 +586,7 @@ export default {
"canDestroyAllObjectRecords": [
6
],
"permissionFlags": [
"permissionFlagGrants": [
27
],
"objectPermissions": [
@@ -7611,12 +7611,12 @@ export default {
]
}
],
"upsertPermissionFlags": [
"upsertPermissionFlagGrants": [
27,
{
"upsertPermissionFlagsInput": [
"upsertPermissionFlagGrantsInput": [
413,
"UpsertPermissionFlagsInput!"
"UpsertPermissionFlagGrantsInput!"
]
}
],
@@ -10251,11 +10251,11 @@ export default {
1
]
},
"UpsertPermissionFlagsInput": {
"UpsertPermissionFlagGrantsInput": {
"roleId": [
3
],
"permissionFlagKeys": [
"permissionFlagGrantKeys": [
18
],
"__typename": [
@@ -9,7 +9,7 @@ A **role** is a permission set: which objects an app can read or write, which fi
```ts src/roles/restricted-company-role.ts
import {
defineRole,
PermissionFlag,
PermissionFlagGrant,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
} from 'twenty-sdk/define';
@@ -45,7 +45,7 @@ export default defineRole({
canUpdateFieldValue: false,
},
],
permissionFlags: [PermissionFlag.APPLICATIONS],
permissionFlagGrants: [PermissionFlagGrant.APPLICATIONS],
});
```
@@ -54,7 +54,7 @@ export default defineRole({
When you scaffold a new app, the CLI creates a default role file declared with `defineApplicationRole()`:
```ts src/roles/default-role.ts
import { defineApplicationRole, PermissionFlag } from 'twenty-sdk/define';
import { defineApplicationRole, PermissionFlagGrant } from 'twenty-sdk/define';
export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER =
'b648f87b-1d26-4961-b974-0908fd991061';
@@ -73,7 +73,7 @@ export default defineApplicationRole({
canBeAssignedToApiKeys: false,
objectPermissions: [],
fieldPermissions: [],
permissionFlags: [],
permissionFlagGrants: [],
});
```
@@ -88,5 +88,5 @@ Notes:
- Start from the scaffolded role, then progressively restrict it — the default grants broad read access, which is rarely what you want in production.
- Replace `objectPermissions` and `fieldPermissions` with the exact objects and fields your functions actually need.
- `permissionFlags` control access to platform-level capabilities. Keep them minimal.
- `permissionFlagGrants` control access to platform-level capabilities. Keep them minimal.
- See a working example: [`hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts).
@@ -57,7 +57,7 @@ const GET_ROLES_QUERY = `
expiresAt
revokedAt
}
permissionFlags {
permissionFlagGrants {
__typename
id
flag
File diff suppressed because one or more lines are too long
@@ -19,7 +19,7 @@ import {
filterAttachmentsWithFile,
} from '@/activities/files/utils/filterAttachmentsWithFile';
import { getAttachmentUrl } from '@/activities/utils/getAttachmentUrl';
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { useHasPermissionFlagGrant } from '@/settings/roles/hooks/useHasPermissionFlagGrant';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
import { isDefined } from 'twenty-shared/utils';
@@ -129,11 +129,11 @@ export const AttachmentList = ({
isAttachmentPreviewEnabledState,
);
const hasDownloadPermission = useHasPermissionFlag(
const hasDownloadPermission = useHasPermissionFlagGrant(
PermissionFlagType.DOWNLOAD_FILE,
);
const hasUploadPermission = useHasPermissionFlag(
const hasUploadPermission = useHasPermissionFlagGrant(
PermissionFlagType.UPLOAD_FILE,
);
@@ -15,7 +15,7 @@ import { getSafeUrl, isDefined } from 'twenty-shared/utils';
import { type AttachmentWithFile } from '@/activities/files/utils/filterAttachmentsWithFile';
import { FileIcon } from '@/file/components/FileIcon';
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { useHasPermissionFlagGrant } from '@/settings/roles/hooks/useHasPermissionFlagGrant';
import { CoreObjectNameSingular } from 'twenty-shared/types';
import { IconCalendar, OverflowingTextWithTooltip } from 'twenty-ui/display';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
@@ -83,7 +83,7 @@ export const AttachmentRow = ({
const { theme } = useContext(ThemeContext);
const [isEditing, setIsEditing] = useState(false);
const hasDownloadPermission = useHasPermissionFlag(
const hasDownloadPermission = useHasPermissionFlagGrant(
PermissionFlagType.DOWNLOAD_FILE,
);
@@ -8,7 +8,7 @@ import { useAttachments } from '@/activities/files/hooks/useAttachments';
import { useUploadAttachmentFile } from '@/activities/files/hooks/useUploadAttachmentFile';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { useHasPermissionFlagGrant } from '@/settings/roles/hooks/useHasPermissionFlagGrant';
import { useTargetRecord } from '@/ui/layout/contexts/useTargetRecord';
import { Trans, useLingui } from '@lingui/react/macro';
import { isDefined } from 'twenty-shared/utils';
@@ -82,7 +82,7 @@ export const FilesCard = () => {
const hasObjectUpdatePermissions = objectPermissions.canUpdateObjectRecords;
const hasUploadPermission = useHasPermissionFlag(
const hasUploadPermission = useHasPermissionFlagGrant(
PermissionFlagType.UPLOAD_FILE,
);
@@ -4,7 +4,7 @@ import { useNumberFormat } from '@/localization/hooks/useNumberFormat';
import { useEndSubscriptionTrialPeriod } from '@/settings/billing/hooks/useEndSubscriptionTrialPeriod';
import { useGetNextResourceCreditPrice } from '@/settings/billing/hooks/useGetNextResourceCreditPrice';
import { useGetNextMeteredBillingPrice } from '@/settings/billing/hooks/useGetNextMeteredBillingPrice';
import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap';
import { usePermissionFlagGrantMap } from '@/settings/roles/hooks/usePermissionFlagGrantMap';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
@@ -52,7 +52,7 @@ export const AIChatNoMoreBillingCreditsBanner = () => {
);
const { [PermissionFlagType.WORKSPACE]: hasPermissionToManageBilling } =
usePermissionFlagMap();
usePermissionFlagGrantMap();
if (!hasPermissionToManageBilling) {
return null;
@@ -17,7 +17,7 @@ import { hasTriggeredCreateForDraftState } from '@/ai/states/hasTriggeredCreateF
import { sortChatThreadsByLastActivityDesc } from '@/ai/utils/sortChatThreadsByLastActivityDesc';
import { useUpdateMetadataStoreDraft } from '@/metadata-store/hooks/useUpdateMetadataStoreDraft';
import { metadataStoreState } from '@/metadata-store/states/metadataStoreState';
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { useHasPermissionFlagGrant } from '@/settings/roles/hooks/useHasPermissionFlagGrant';
import { useAtomComponentFamilyStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilyStateCallbackState';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
@@ -31,7 +31,7 @@ import {
export const AgentChatThreadInitializationEffect = () => {
const client = useApolloClient();
const { replaceDraft, applyChanges } = useUpdateMetadataStoreDraft();
const hasAiSettingsPermission = useHasPermissionFlag(
const hasAiSettingsPermission = useHasPermissionFlagGrant(
PermissionFlagType.AI_SETTINGS,
);
@@ -1,5 +1,5 @@
import { AiChatBanner } from '@/ai/components/AiChatBanner';
import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap';
import { usePermissionFlagGrantMap } from '@/settings/roles/hooks/usePermissionFlagGrantMap';
import { useSubscriptionStatus } from '@/workspace/hooks/useSubscriptionStatus';
import { t } from '@lingui/core/macro';
import { SettingsPath } from 'twenty-shared/types';
@@ -17,7 +17,7 @@ export const AiChatCreditsExhaustedMessage = () => {
const isTrialing = subscriptionStatus === SubscriptionStatus.Trialing;
const { [PermissionFlagType.WORKSPACE]: hasPermissionToManageBilling } =
usePermissionFlagMap();
usePermissionFlagGrantMap();
const handleUpgradeClick = () => {
navigateSettings(SettingsPath.Billing);
@@ -4,7 +4,7 @@ import { type UserWorkspace } from '~/generated-metadata/graphql';
export type CurrentUserWorkspace = Pick<
UserWorkspace,
'permissionFlags' | 'twoFactorAuthenticationMethodSummary'
'permissionFlagGrants' | 'twoFactorAuthenticationMethodSummary'
> & {
objectsPermissions: Array<ObjectPermissions & { objectMetadataId: string }>;
};
@@ -40,10 +40,10 @@ export const StandalonePageCommandMenu = () => {
featureFlags[flag.key] = flag.value === true;
}
const permissionFlags: Record<string, boolean> = {};
const permissionFlagGrants: Record<string, boolean> = {};
for (const flag of currentUserWorkspace?.permissionFlags ?? []) {
permissionFlags[flag] = true;
for (const flag of currentUserWorkspace?.permissionFlagGrants ?? []) {
permissionFlagGrants[flag] = true;
}
const targetObjectReadPermissions: Record<string, boolean> = {};
@@ -82,7 +82,7 @@ export const StandalonePageCommandMenu = () => {
},
selectedRecords: [],
featureFlags,
permissionFlags,
permissionFlagGrants,
targetObjectReadPermissions,
targetObjectWritePermissions,
objectMetadataItem: {},
@@ -90,7 +90,7 @@ export const StandalonePageCommandMenu = () => {
};
}, [
currentWorkspace?.featureFlags,
currentUserWorkspace?.permissionFlags,
currentUserWorkspace?.permissionFlagGrants,
isLayoutCustomizationModeEnabled,
objectMetadataItems,
store,
@@ -24,7 +24,7 @@ export const EMPTY_COMMAND_MENU_CONTEXT_API: CommandMenuContextApi = {
},
selectedRecords: [],
featureFlags: {},
permissionFlags: {},
permissionFlagGrants: {},
targetObjectReadPermissions: {},
targetObjectWritePermissions: {},
objectMetadataItem: {},
@@ -64,7 +64,7 @@ const getWrapper =
},
selectedRecords: [],
featureFlags: {},
permissionFlags: {},
permissionFlagGrants: {},
targetObjectReadPermissions: {},
targetObjectWritePermissions: {},
objectMetadataItem: {},
@@ -145,10 +145,10 @@ export const useCurrentCommandMenuContextApi = (): CommandMenuContextApi => {
const currentUserWorkspace = useAtomStateValue(currentUserWorkspaceState);
const permissionFlags: Record<string, boolean> = {};
const permissionFlagGrants: Record<string, boolean> = {};
for (const flag of currentUserWorkspace?.permissionFlags ?? []) {
permissionFlags[flag] = true;
for (const flag of currentUserWorkspace?.permissionFlagGrants ?? []) {
permissionFlagGrants[flag] = true;
}
const targetObjectReadPermissions: Record<string, boolean> = {};
@@ -185,7 +185,7 @@ export const useCurrentCommandMenuContextApi = (): CommandMenuContextApi => {
objectPermissions,
selectedRecords,
featureFlags,
permissionFlags,
permissionFlagGrants,
targetObjectReadPermissions,
targetObjectWritePermissions,
objectMetadataItem: objectMetadataItem ?? {},
@@ -10,7 +10,7 @@ import { InformationBannerInvalidEnterpriseKey } from '@/information-banner/comp
import { InformationBannerMaintenance } from '@/information-banner/components/maintenance/InformationBannerMaintenance';
import { InformationBannerReconnectAccountEmailAliases } from '@/information-banner/components/reconnect-account/InformationBannerReconnectAccountEmailAliases';
import { InformationBannerReconnectAccountInsufficientPermissions } from '@/information-banner/components/reconnect-account/InformationBannerReconnectAccountInsufficientPermissions';
import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap';
import { usePermissionFlagGrantMap } from '@/settings/roles/hooks/usePermissionFlagGrantMap';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useIsWorkspaceActivationStatusEqualsTo } from '@/workspace/hooks/useIsWorkspaceActivationStatusEqualsTo';
import { useSubscriptionStatus } from '@/workspace/hooks/useSubscriptionStatus';
@@ -32,7 +32,7 @@ const StyledInformationBannerWrapper = styled.div`
export const InformationBannerWrapper = () => {
const subscriptionStatus = useSubscriptionStatus();
const permissionMap = usePermissionFlagMap();
const permissionMap = usePermissionFlagGrantMap();
const isAccountSyncEnabled =
permissionMap[PermissionFlagType.CONNECTED_ACCOUNTS];
const isWorkspaceSuspended = useIsWorkspaceActivationStatusEqualsTo(
@@ -1,7 +1,7 @@
import { useSnackBarOnQueryError } from '@/apollo/hooks/useSnackBarOnQueryError';
import { useRedirect } from '@/domain-manager/hooks/useRedirect';
import { InformationBanner } from '@/information-banner/components/InformationBanner';
import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap';
import { usePermissionFlagGrantMap } from '@/settings/roles/hooks/usePermissionFlagGrantMap';
import { t } from '@lingui/core/macro';
import { SettingsPath } from 'twenty-shared/types';
import { getSettingsPath, isDefined } from 'twenty-shared/utils';
@@ -24,7 +24,7 @@ export const InformationBannerBillingSubscriptionPaused = () => {
const {
[PermissionFlagType.WORKSPACE]: hasPermissionToUpdateBillingDetails,
} = usePermissionFlagMap();
} = usePermissionFlagGrantMap();
const openBillingPortal = () => {
if (isDefined(data) && isDefined(data.billingPortalSession.url)) {
@@ -1,6 +1,6 @@
import { useEndSubscriptionTrialPeriod } from '@/settings/billing/hooks/useEndSubscriptionTrialPeriod';
import { InformationBanner } from '@/information-banner/components/InformationBanner';
import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap';
import { usePermissionFlagGrantMap } from '@/settings/roles/hooks/usePermissionFlagGrantMap';
import { useLingui } from '@lingui/react/macro';
import { PermissionFlagType } from '~/generated-metadata/graphql';
@@ -9,7 +9,7 @@ export const InformationBannerEndTrialPeriod = () => {
const { t } = useLingui();
const { [PermissionFlagType.WORKSPACE]: hasPermissionToEndTrialPeriod } =
usePermissionFlagMap();
usePermissionFlagGrantMap();
return (
<InformationBanner
@@ -1,6 +1,6 @@
import { useRedirect } from '@/domain-manager/hooks/useRedirect';
import { InformationBanner } from '@/information-banner/components/InformationBanner';
import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap';
import { usePermissionFlagGrantMap } from '@/settings/roles/hooks/usePermissionFlagGrantMap';
import { t } from '@lingui/core/macro';
import { SettingsPath } from 'twenty-shared/types';
import { getSettingsPath, isDefined } from 'twenty-shared/utils';
@@ -21,7 +21,7 @@ export const InformationBannerFailPaymentInfo = () => {
const {
[PermissionFlagType.WORKSPACE]: hasPermissionToUpdateBillingDetails,
} = usePermissionFlagMap();
} = usePermissionFlagGrantMap();
const openBillingPortal = () => {
if (isDefined(data) && isDefined(data.billingPortalSession.url)) {
@@ -1,7 +1,7 @@
import { BILLING_CHECKOUT_SESSION_DEFAULT_VALUE } from '@/settings/billing/constants/BillingCheckoutSessionDefaultValue';
import { useHandleCheckoutSession } from '@/settings/billing/hooks/useHandleCheckoutSession';
import { InformationBanner } from '@/information-banner/components/InformationBanner';
import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap';
import { usePermissionFlagGrantMap } from '@/settings/roles/hooks/usePermissionFlagGrantMap';
import { t } from '@lingui/core/macro';
import { SettingsPath } from 'twenty-shared/types';
import { getSettingsPath } from 'twenty-shared/utils';
@@ -16,7 +16,7 @@ export const InformationBannerNoBillingSubscription = () => {
});
const { [PermissionFlagType.WORKSPACE]: hasPermissionToSubscribe } =
usePermissionFlagMap();
usePermissionFlagGrantMap();
return (
<InformationBanner
@@ -1,5 +1,5 @@
import { InformationBanner } from '@/information-banner/components/InformationBanner';
import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap';
import { usePermissionFlagGrantMap } from '@/settings/roles/hooks/usePermissionFlagGrantMap';
import { useLingui } from '@lingui/react/macro';
import { SettingsPath } from 'twenty-shared/types';
import { PermissionFlagType } from '~/generated-metadata/graphql';
@@ -9,7 +9,7 @@ export const InformationBannerNoMoreCredits = () => {
const { t } = useLingui();
const { [PermissionFlagType.WORKSPACE]: hasPermissionToUpdateCreditPlan } =
usePermissionFlagMap();
usePermissionFlagGrantMap();
const navigateSettings = useNavigateSettings();
@@ -30,6 +30,7 @@ export const useMetadataErrorHandler = () => {
viewFilter: t`view filter`,
index: t`index`,
logicFunction: t`logic function`,
permissionFlagGrant: t`permission flag grant`,
permissionFlag: t`permission flag`,
objectPermission: t`object permission`,
fieldPermission: t`field permission`,
@@ -104,8 +104,9 @@ export const UserMetadataProviderInitialEffect = () => {
if (isDefined(userQueryData.currentUser.currentUserWorkspace)) {
setCurrentUserWorkspace({
permissionFlags:
userQueryData.currentUser.currentUserWorkspace.permissionFlags ?? [],
permissionFlagGrants:
userQueryData.currentUser.currentUserWorkspace.permissionFlagGrants ??
[],
twoFactorAuthenticationMethodSummary:
userQueryData.currentUser.currentUserWorkspace
.twoFactorAuthenticationMethodSummary ?? [],
@@ -7,7 +7,7 @@ export type FlatRole = Omit<
| 'workspaceMembers'
| 'fieldPermissions'
| 'objectPermissions'
| 'permissionFlags'
| 'permissionFlagGrants'
| 'rowLevelPermissionPredicateGroups'
| 'rowLevelPermissionPredicates'
>;
@@ -2,7 +2,7 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { NavigationDrawerAiChatContent } from '@/ai/components/NavigationDrawerAiChatContent';
import { MainNavigationDrawerNavigationContent } from '@/navigation/components/MainNavigationDrawerNavigationContent';
import { MainNavigationDrawerTabsRow } from '@/navigation/components/MainNavigationDrawerTabsRow';
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { useHasPermissionFlagGrant } from '@/settings/roles/hooks/useHasPermissionFlagGrant';
import { NavigationDrawer } from '@/ui/navigation/navigation-drawer/components/NavigationDrawer';
import { NavigationDrawerFixedContent } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerFixedContent';
import { NavigationDrawerScrollableContent } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerScrollableContent';
@@ -16,7 +16,7 @@ export const MainNavigationDrawer = ({ className }: { className?: string }) => {
navigationDrawerActiveTabState,
);
const currentWorkspace = useAtomStateValue(currentWorkspaceState);
const hasAiSettingsPermission = useHasPermissionFlag(
const hasAiSettingsPermission = useHasPermissionFlagGrant(
PermissionFlagType.AI_SETTINGS,
);
@@ -12,7 +12,7 @@ import { useIsMobile } from 'twenty-ui/utilities';
import { useContext } from 'react';
import { useSwitchToNewAiChat } from '@/ai/hooks/useSwitchToNewAiChat';
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { useHasPermissionFlagGrant } from '@/settings/roles/hooks/useHasPermissionFlagGrant';
import { NavigationDrawerAnimatedCollapseWrapper } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerAnimatedCollapseWrapper';
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
import { navigationDrawerActiveTabState } from '@/ui/navigation/states/navigationDrawerActiveTabState';
@@ -144,7 +144,7 @@ export const MainNavigationDrawerTabsRow = () => {
const setIsNavigationDrawerExpanded = useSetAtomState(
isNavigationDrawerExpandedState,
);
const hasAiSettingsPermission = useHasPermissionFlag(
const hasAiSettingsPermission = useHasPermissionFlagGrant(
PermissionFlagType.AI_SETTINGS,
);
@@ -5,7 +5,7 @@ import { useDefaultHomePagePath } from '@/navigation/hooks/useDefaultHomePagePat
import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage';
import { currentMobileNavigationDrawerState } from '@/navigation/states/currentMobileNavigationDrawerState';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { useHasPermissionFlagGrant } from '@/settings/roles/hooks/useHasPermissionFlagGrant';
import { useOpenRecordsSearchPageInSidePanel } from '@/side-panel/hooks/useOpenRecordsSearchPageInSidePanel';
import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu';
import { isSidePanelOpenedState } from '@/side-panel/states/isSidePanelOpenedState';
@@ -39,7 +39,7 @@ export const MobileNavigationBar = () => {
const { switchToNewChat } = useSwitchToNewAiChat();
const { alphaSortedActiveNonSystemObjectMetadataItems } =
useFilteredObjectMetadataItems();
const hasAiSettingsPermission = useHasPermissionFlag(
const hasAiSettingsPermission = useHasPermissionFlagGrant(
PermissionFlagType.AI_SETTINGS,
);
@@ -151,7 +151,7 @@ export const responseData = {
},
workspaceMembers: [],
currentUserWorkspace: {
permissionFlags: ['DATA_MODEL'],
permissionFlagGrants: ['DATA_MODEL'],
},
currentWorkspace: {
id: 'test-workspace-id',
@@ -1,6 +1,6 @@
import { OBJECT_OPTIONS_DROPDOWN_ID } from '@/object-record/object-options-dropdown/constants/ObjectOptionsDropdownId';
import { useObjectOptionsDropdown } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsDropdown';
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { useHasPermissionFlagGrant } from '@/settings/roles/hooks/useHasPermissionFlagGrant';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
@@ -36,7 +36,9 @@ export const ObjectOptionsDropdownVisibilityContent = () => {
const { currentView } = useGetCurrentViewOnly();
const { updateCurrentView } = useUpdateCurrentView();
const { copyToClipboard } = useCopyToClipboard();
const hasViewsPermission = useHasPermissionFlag(PermissionFlagType.VIEWS);
const hasViewsPermission = useHasPermissionFlagGrant(
PermissionFlagType.VIEWS,
);
const { canPersistChanges } = useCanPersistViewChanges();
const selectedItemId = useAtomComponentStateValue(
@@ -1,4 +1,4 @@
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { useHasPermissionFlagGrant } from '@/settings/roles/hooks/useHasPermissionFlagGrant';
import { isNonEmptyString } from '@sniptt/guards';
import { useParams } from 'react-router-dom';
import { PermissionFlagType } from '~/generated-metadata/graphql';
@@ -6,7 +6,7 @@ import { PermissionFlagType } from '~/generated-metadata/graphql';
export const useCanAddSelectOption = (fieldName: string) => {
const { objectNamePlural } = useParams();
const userHasPermissionToEditDataModel = useHasPermissionFlag(
const userHasPermissionToEditDataModel = useHasPermissionFlagGrant(
PermissionFlagType.DATA_MODEL,
);
@@ -7,7 +7,7 @@ import { type RecordGroupAction } from '@/object-record/record-group/types/Recor
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { useRecordIndexIdFromCurrentContextStore } from '@/object-record/record-index/hooks/useRecordIndexIdFromCurrentContextStore';
import { recordIndexGroupFieldMetadataItemComponentState } from '@/object-record/record-index/states/recordIndexGroupFieldMetadataComponentState';
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { useHasPermissionFlagGrant } from '@/settings/roles/hooks/useHasPermissionFlagGrant';
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
import { useAtomComponentFamilySelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilySelectorValue';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
@@ -90,7 +90,7 @@ export const useRecordGroupActions = ({
recordIndexGroupFieldMetadataItem,
]);
const hasAccessToDataModelSettings = useHasPermissionFlag(
const hasAccessToDataModelSettings = useHasPermissionFlagGrant(
PermissionFlagType.DATA_MODEL,
);
const currentIndex = visibleRecordGroupIds.findIndex(
@@ -31,7 +31,8 @@ const renderHooks = (
onboardingStatus: OnboardingStatus,
withCurrentBillingSubscription: boolean,
withOneWorkspaceMember = true,
permissionFlags = mockedUserData.currentUserWorkspace.permissionFlags,
permissionFlagGrants = mockedUserData.currentUserWorkspace
.permissionFlagGrants,
) => {
const { result } = renderHook(
() => {
@@ -57,7 +58,7 @@ const renderHooks = (
result.current.setCurrentUser({ ...mockedUserData, onboardingStatus });
result.current.setCurrentUserWorkspace({
...mockedUserData.currentUserWorkspace,
permissionFlags,
permissionFlagGrants,
});
result.current.setCurrentWorkspace({
...mockCurrentWorkspace,
@@ -9,7 +9,7 @@ import {
currentWorkspaceState,
} from '@/auth/states/currentWorkspaceState';
import { calendarBookingPageIdState } from '@/client-config/states/calendarBookingPageIdState';
import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap';
import { usePermissionFlagGrantMap } from '@/settings/roles/hooks/usePermissionFlagGrantMap';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useCallback } from 'react';
@@ -67,7 +67,7 @@ export const useSetNextOnboardingStatus = () => {
const currentUser = useAtomStateValue(currentUserState);
const currentWorkspace = useAtomStateValue(currentWorkspaceState);
const calendarBookingPageId = useAtomStateValue(calendarBookingPageIdState);
const permissionMap = usePermissionFlagMap();
const permissionMap = usePermissionFlagGrantMap();
const isAccountSyncEnabled =
permissionMap[PermissionFlagType.CONNECTED_ACCOUNTS];
@@ -1708,7 +1708,7 @@ export const Catalog: CatalogStory<Story, typeof WidgetRenderer> = {
// currentUserWorkspaceState is now a Jotai state, set it directly
if (isRestricted === true) {
jotaiStore.set(currentUserWorkspaceState.atom, {
permissionFlags: [],
permissionFlagGrants: [],
twoFactorAuthenticationMethodSummary: null,
objectsPermissions: [
{
@@ -1725,7 +1725,7 @@ export const Catalog: CatalogStory<Story, typeof WidgetRenderer> = {
});
} else {
jotaiStore.set(currentUserWorkspaceState.atom, {
permissionFlags: [],
permissionFlagGrants: [],
twoFactorAuthenticationMethodSummary: null,
objectsPermissions: [
{
@@ -24,7 +24,7 @@ import { useNextBillingPhase } from '@/settings/billing/hooks/useNextBillingPhas
import { useNextBillingSeats } from '@/settings/billing/hooks/useNextBillingSeats';
import { useNextPlan } from '@/settings/billing/hooks/useNextPlan';
import { useSplitPhaseItemsInPrices } from '@/settings/billing/hooks/useSplitPhaseItemsInPrices';
import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap';
import { usePermissionFlagGrantMap } from '@/settings/roles/hooks/usePermissionFlagGrantMap';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
@@ -190,7 +190,7 @@ export const SettingsBillingSubscriptionInfo = ({
useEndSubscriptionTrialPeriod();
const { [PermissionFlagType.WORKSPACE]: hasPermissionToEndTrialPeriod } =
usePermissionFlagMap();
usePermissionFlagGrantMap();
const seats = currentBillingSubscription.billingSubscriptionItems?.find(
(item) =>
@@ -1,5 +1,5 @@
import { useHasAccessTokenPair } from '@/auth/hooks/useHasAccessTokenPair';
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { useHasPermissionFlagGrant } from '@/settings/roles/hooks/useHasPermissionFlagGrant';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { type ReactNode } from 'react';
import { Navigate, Outlet } from 'react-router-dom';
@@ -22,7 +22,7 @@ export const SettingsProtectedRouteWrapper = ({
requiredFeatureFlag,
}: SettingsProtectedRouteWrapperProps) => {
const hasAccessTokenPair = useHasAccessTokenPair();
const hasPermission = useHasPermissionFlag(settingsPermission);
const hasPermission = useHasPermissionFlagGrant(settingsPermission);
const requiredFeatureFlagEnabled = useIsFeatureEnabled(
requiredFeatureFlag || null,
);
@@ -12,7 +12,7 @@ import {
import { currentUserState } from '@/auth/states/currentUserState';
import { billingState } from '@/client-config/states/billingState';
import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap';
import { usePermissionFlagGrantMap } from '@/settings/roles/hooks/usePermissionFlagGrantMap';
import { SnackBarComponentInstanceContext } from '@/ui/feedback/snack-bar-manager/contexts/SnackBarComponentInstanceContext';
import {
jotaiStore,
@@ -65,8 +65,8 @@ const Wrapper = ({ children }: { children: ReactNode }) => (
</MockedProvider>
);
jest.mock('@/settings/roles/hooks/usePermissionFlagMap', () => ({
usePermissionFlagMap: jest.fn(),
jest.mock('@/settings/roles/hooks/usePermissionFlagGrantMap', () => ({
usePermissionFlagGrantMap: jest.fn(),
}));
jest.mock('@/domain-manager/hooks/useRedirectToWorkspaceDomain', () => ({
@@ -83,7 +83,7 @@ describe('useSettingsNavigationItems', () => {
});
it('should hide workspace settings when no permissions', () => {
(usePermissionFlagMap as jest.Mock).mockImplementation(() => ({
(usePermissionFlagGrantMap as jest.Mock).mockImplementation(() => ({
[PermissionFlagType.WORKSPACE]: false,
[PermissionFlagType.WORKSPACE_MEMBERS]: false,
[PermissionFlagType.DATA_MODEL]: false,
@@ -105,7 +105,7 @@ describe('useSettingsNavigationItems', () => {
});
it('should show workspace settings when has permissions', () => {
(usePermissionFlagMap as jest.Mock).mockImplementation(() => ({
(usePermissionFlagGrantMap as jest.Mock).mockImplementation(() => ({
[PermissionFlagType.WORKSPACE]: true,
[PermissionFlagType.WORKSPACE_MEMBERS]: true,
[PermissionFlagType.DATA_MODEL]: true,
@@ -127,7 +127,7 @@ describe('useSettingsNavigationItems', () => {
});
it('should show user section items regardless of permissions', () => {
(usePermissionFlagMap as jest.Mock).mockImplementation(() => ({
(usePermissionFlagGrantMap as jest.Mock).mockImplementation(() => ({
[PermissionFlagType.WORKSPACE]: false,
[PermissionFlagType.WORKSPACE_MEMBERS]: false,
[PermissionFlagType.DATA_MODEL]: false,
@@ -5,7 +5,7 @@ import { currentUserState } from '@/auth/states/currentUserState';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { billingState } from '@/client-config/states/billingState';
import { supportChatState } from '@/client-config/states/supportChatState';
import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap';
import { usePermissionFlagGrantMap } from '@/settings/roles/hooks/usePermissionFlagGrantMap';
import { getDocumentationUrl } from '@/support/utils/getDocumentationUrl';
import {
type NavigationDrawerItemIndentationLevel,
@@ -72,7 +72,7 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
supportChat?.supportDriver === 'FRONT' &&
isNonEmptyString(supportChat.supportFrontChatId);
const permissionMap = usePermissionFlagMap();
const permissionMap = usePermissionFlagGrantMap();
return [
{
label: t`User`,
@@ -14,9 +14,10 @@ export const useCanChangePassword = () => {
return { canChangePassword: true };
}
const hasBypassPermission = currentUserWorkspace?.permissionFlags?.includes(
PermissionFlagType.SSO_BYPASS,
);
const hasBypassPermission =
currentUserWorkspace?.permissionFlagGrants?.includes(
PermissionFlagType.SSO_BYPASS,
);
if (!hasBypassPermission) {
return { canChangePassword: false };
@@ -23,8 +23,8 @@ export const useCanEditProfileField = (field: EditableProfileField) => {
const editableFields = currentWorkspace.editableProfileFields ?? [];
const workspaceAllowsField = editableFields.includes(field);
const permissionFlags = currentUserWorkspace.permissionFlags ?? [];
const hasProfilePermission = permissionFlags.includes(
const permissionFlagGrants = currentUserWorkspace.permissionFlagGrants ?? [];
const hasProfilePermission = permissionFlagGrants.includes(
PermissionFlagType.PROFILE_INFORMATION,
);
@@ -1,9 +0,0 @@
import { gql } from '@apollo/client';
export const PERMISSION_FLAG_FRAGMENT = gql`
fragment PermissionFlagFragment on PermissionFlag {
id
flag
roleId
}
`;
@@ -0,0 +1,9 @@
import { gql } from '@apollo/client';
export const PERMISSION_FLAG_GRANT_FRAGMENT = gql`
fragment PermissionFlagGrantFragment on PermissionFlagGrant {
id
flag
roleId
}
`;
@@ -0,0 +1,15 @@
import { PERMISSION_FLAG_GRANT_FRAGMENT } from '@/settings/roles/graphql/fragments/permissionFlagGrantFragment';
import { gql } from '@apollo/client';
export const UPSERT_PERMISSION_FLAG_GRANTS = gql`
${PERMISSION_FLAG_GRANT_FRAGMENT}
mutation UpsertPermissionFlagGrants(
$upsertPermissionFlagGrantsInput: UpsertPermissionFlagGrantsInput!
) {
upsertPermissionFlagGrants(
upsertPermissionFlagGrantsInput: $upsertPermissionFlagGrantsInput
) {
...PermissionFlagGrantFragment
}
}
`;
@@ -1,15 +0,0 @@
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
}
}
`;
@@ -2,7 +2,7 @@ import { AGENT_FRAGMENT } from '@/ai/graphql/fragments/agentFragment';
import { API_KEY_FOR_ROLE_FRAGMENT } from '@/settings/roles/graphql/fragments/apiKeyForRoleFragment';
import { FIELD_PERMISSION_FRAGMENT } from '@/settings/roles/graphql/fragments/fieldPermissionFragment';
import { OBJECT_PERMISSION_FRAGMENT } from '@/settings/roles/graphql/fragments/objectPermissionFragment';
import { PERMISSION_FLAG_FRAGMENT } from '@/settings/roles/graphql/fragments/permissionFlagFragment';
import { PERMISSION_FLAG_GRANT_FRAGMENT } from '@/settings/roles/graphql/fragments/permissionFlagGrantFragment';
import { ROLE_FRAGMENT } from '@/settings/roles/graphql/fragments/roleFragment';
import { ROW_LEVEL_PERMISSION_PREDICATE_FRAGMENT } from '@/settings/roles/graphql/fragments/rowLevelPermissionPredicateFragment';
import { ROW_LEVEL_PERMISSION_PREDICATE_GROUP_FRAGMENT } from '@/settings/roles/graphql/fragments/rowLevelPermissionPredicateGroupFragment';
@@ -14,7 +14,7 @@ export const GET_ROLES = gql`
${ROLE_FRAGMENT}
${AGENT_FRAGMENT}
${API_KEY_FOR_ROLE_FRAGMENT}
${PERMISSION_FLAG_FRAGMENT}
${PERMISSION_FLAG_GRANT_FRAGMENT}
${OBJECT_PERMISSION_FRAGMENT}
${FIELD_PERMISSION_FRAGMENT}
${ROW_LEVEL_PERMISSION_PREDICATE_FRAGMENT}
@@ -31,8 +31,8 @@ export const GET_ROLES = gql`
apiKeys {
...ApiKeyForRoleFragment
}
permissionFlags {
...PermissionFlagFragment
permissionFlagGrants {
...PermissionFlagGrantFragment
}
objectPermissions {
...ObjectPermissionFragment
@@ -6,22 +6,24 @@ import {
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
export const useHasPermissionFlag = (permissionFlag?: PermissionFlagType) => {
export const useHasPermissionFlagGrant = (
permissionFlagKey?: PermissionFlagType,
) => {
const currentWorkspace = useAtomStateValue(currentWorkspaceState);
const currentUserWorkspace = useAtomStateValue(currentUserWorkspaceState);
if (!permissionFlag) {
if (!permissionFlagKey) {
return true;
}
if (
permissionFlag === PermissionFlagType.WORKSPACE &&
permissionFlagKey === PermissionFlagType.WORKSPACE &&
currentWorkspace?.activationStatus ===
WorkspaceActivationStatus.PENDING_CREATION
) {
return true;
}
const userFlags = currentUserWorkspace?.permissionFlags ?? [];
return userFlags.includes(permissionFlag);
const userFlags = currentUserWorkspace?.permissionFlagGrants ?? [];
return userFlags.includes(permissionFlagKey);
};
@@ -3,11 +3,14 @@ import { PermissionFlagType } from '~/generated-metadata/graphql';
import { buildRecordFromKeysWithSameValue } from '~/utils/array/buildRecordFromKeysWithSameValue';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
export const usePermissionFlagMap = (): Record<PermissionFlagType, boolean> => {
export const usePermissionFlagGrantMap = (): Record<
PermissionFlagType,
boolean
> => {
const currentUserWorkspace = useAtomStateValue(currentUserWorkspaceState);
const currentUserWorkspaceSettingsPermissions =
currentUserWorkspace?.permissionFlags;
currentUserWorkspace?.permissionFlagGrants;
const initialPermissions = buildRecordFromKeysWithSameValue(
Object.values(PermissionFlagType),
@@ -1,7 +1,7 @@
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 { useSettingsRolePermissionFlagConfig } from '@/settings/roles/role-permissions/permission-flags/hooks/useSettingsRolePermissionFlagConfig';
import { useSettingsRolePermissionFlagGrantConfig } from '@/settings/roles/role-permissions/permission-flags/hooks/useSettingsRolePermissionFlagGrantConfig';
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
import { useSetAtomFamilyState } from '@/ui/utilities/state/jotai/hooks/useSetAtomFamilyState';
@@ -42,7 +42,7 @@ export const SettingsRolePermissionsSettingsSection = ({
roleId,
);
const settingsPermissionsConfig = useSettingsRolePermissionFlagConfig({
const settingsPermissionsConfig = useSettingsRolePermissionFlagGrantConfig({
assignmentCapabilities: {
canBeAssignedToAgents: settingsDraftRole.canBeAssignedToAgents,
canBeAssignedToUsers: settingsDraftRole.canBeAssignedToUsers,
@@ -31,15 +31,15 @@ export const SettingsRolePermissionsSettingsTableHeader = ({
);
const allSettingsPermissionsEnabled = settingsPermissionsConfig.every(
(permission) =>
settingsDraftRole.permissionFlags?.some(
(permissionFlag) => permissionFlag.flag === permission.key,
settingsDraftRole.permissionFlagGrants?.some(
(permissionFlagGrant) => permissionFlagGrant.flag === permission.key,
),
);
const someSettingsPermissionsEnabled = settingsPermissionsConfig.some(
(permission) =>
settingsDraftRole.permissionFlags?.some(
(permissionFlag) => permissionFlag.flag === permission.key,
settingsDraftRole.permissionFlagGrants?.some(
(permissionFlagGrant) => permissionFlagGrant.flag === permission.key,
),
);
@@ -64,7 +64,7 @@ export const SettingsRolePermissionsSettingsTableHeader = ({
setSettingsDraftRole({
...settingsDraftRole,
permissionFlags: newValue
permissionFlagGrants: newValue
? settingsPermissionsConfig.map((permission) => ({
id: v4(),
flag: permission.key,
@@ -47,8 +47,8 @@ export const SettingsRolePermissionsSettingsTableRow = ({
);
const isPermissionEnabled =
settingsDraftRole.permissionFlags?.some(
(permissionFlag) => permissionFlag.flag === permission.key,
settingsDraftRole.permissionFlagGrants?.some(
(permissionFlagGrant) => permissionFlagGrant.flag === permission.key,
) ?? false;
const isAllSettingsOverride =
@@ -64,12 +64,12 @@ export const SettingsRolePermissionsSettingsTableRow = ({
);
const handleChange = (value: boolean) => {
const currentPermissions = settingsDraftRole.permissionFlags ?? [];
const currentPermissions = settingsDraftRole.permissionFlagGrants ?? [];
if (value === true) {
setSettingsDraftRole({
...settingsDraftRole,
permissionFlags: [
permissionFlagGrants: [
...currentPermissions,
{
id: v4(),
@@ -81,7 +81,7 @@ export const SettingsRolePermissionsSettingsTableRow = ({
} else {
setSettingsDraftRole({
...settingsDraftRole,
permissionFlags: currentPermissions.filter(
permissionFlagGrants: currentPermissions.filter(
(p) => p.flag !== permission.key,
),
});
@@ -1,7 +1,7 @@
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 { useActionRolePermissionFlagConfig } from '@/settings/roles/role-permissions/permission-flags/hooks/useActionRolePermissionFlagConfig';
import { useActionRolePermissionFlagGrantConfig } from '@/settings/roles/role-permissions/permission-flags/hooks/useActionRolePermissionFlagGrantConfig';
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
import { useSetAtomFamilyState } from '@/ui/utilities/state/jotai/hooks/useSetAtomFamilyState';
@@ -43,7 +43,7 @@ export const SettingsRolePermissionsToolSection = ({
roleId,
);
const toolPermissionsConfig = useActionRolePermissionFlagConfig({
const toolPermissionsConfig = useActionRolePermissionFlagGrantConfig({
assignmentCapabilities: {
canBeAssignedToAgents: settingsDraftRole.canBeAssignedToAgents,
canBeAssignedToUsers: settingsDraftRole.canBeAssignedToUsers,
@@ -15,7 +15,7 @@ import {
} from 'twenty-ui/display';
import { PermissionFlagType } from '~/generated-metadata/graphql';
type UseActionRolePermissionFlagConfigParams = {
type UseActionRolePermissionFlagGrantConfigParams = {
assignmentCapabilities?: {
canBeAssignedToAgents?: boolean;
canBeAssignedToUsers?: boolean;
@@ -23,9 +23,9 @@ type UseActionRolePermissionFlagConfigParams = {
};
};
export const useActionRolePermissionFlagConfig = ({
export const useActionRolePermissionFlagGrantConfig = ({
assignmentCapabilities,
}: UseActionRolePermissionFlagConfigParams = {}): SettingsRolePermissionsSettingPermission[] => {
}: UseActionRolePermissionFlagGrantConfigParams = {}): SettingsRolePermissionsSettingPermission[] => {
const {
canBeAssignedToAgents = false,
canBeAssignedToUsers = false,
@@ -18,7 +18,7 @@ import {
} from 'twenty-ui/display';
import { PermissionFlagType } from '~/generated-metadata/graphql';
type UseSettingsRolePermissionFlagConfigParams = {
type UseSettingsRolePermissionFlagGrantConfigParams = {
assignmentCapabilities?: {
canBeAssignedToAgents?: boolean;
canBeAssignedToUsers?: boolean;
@@ -26,9 +26,9 @@ type UseSettingsRolePermissionFlagConfigParams = {
};
};
export const useSettingsRolePermissionFlagConfig = ({
export const useSettingsRolePermissionFlagGrantConfig = ({
assignmentCapabilities,
}: UseSettingsRolePermissionFlagConfigParams = {}): SettingsRolePermissionsSettingPermission[] => {
}: UseSettingsRolePermissionFlagGrantConfigParams = {}): SettingsRolePermissionsSettingPermission[] => {
const {
canBeAssignedToAgents = false,
canBeAssignedToUsers = false,
@@ -19,7 +19,7 @@ import {
UpdateOneRoleDocument,
UpsertFieldPermissionsDocument,
UpsertObjectPermissionsDocument,
UpsertPermissionFlagsDocument,
UpsertPermissionFlagGrantsDocument,
} from '~/generated-metadata/graphql';
import { getDirtyFields } from '~/utils/getDirtyFields';
@@ -49,7 +49,9 @@ export const useSaveDraftRoleToDB = ({
}) => {
const [createRole] = useMutation(CreateOneRoleDocument);
const [updateRole] = useMutation(UpdateOneRoleDocument);
const [upsertPermissionFlags] = useMutation(UpsertPermissionFlagsDocument);
const [upsertPermissionFlagGrants] = useMutation(
UpsertPermissionFlagGrantsDocument,
);
const [upsertObjectPermissions] = useMutation(
UpsertObjectPermissionsDocument,
);
@@ -164,14 +166,14 @@ export const useSaveDraftRoleToDB = ({
};
const updateExistingRole = async () => {
if (isDefined(dirtyFields.permissionFlags)) {
await upsertPermissionFlags({
if (isDefined(dirtyFields.permissionFlagGrants)) {
await upsertPermissionFlagGrants({
variables: {
upsertPermissionFlagsInput: {
upsertPermissionFlagGrantsInput: {
roleId: roleId,
permissionFlagKeys:
settingsDraftRole.permissionFlags?.map(
(permissionFlag) => permissionFlag.flag,
permissionFlagGrantKeys:
settingsDraftRole.permissionFlagGrants?.map(
(permissionFlagGrant) => permissionFlagGrant.flag,
) ?? [],
},
},
@@ -361,14 +363,14 @@ export const useSaveDraftRoleToDB = ({
};
const upsertRolePermissions = async (targetRoleId: string) => {
if (isDefined(dirtyFields.permissionFlags)) {
await upsertPermissionFlags({
if (isDefined(dirtyFields.permissionFlagGrants)) {
await upsertPermissionFlagGrants({
variables: {
upsertPermissionFlagsInput: {
upsertPermissionFlagGrantsInput: {
roleId: targetRoleId,
permissionFlagKeys:
settingsDraftRole.permissionFlags?.map(
(permissionFlag) => permissionFlag.flag,
permissionFlagGrantKeys:
settingsDraftRole.permissionFlagGrants?.map(
(permissionFlagGrant) => permissionFlagGrant.flag,
) ?? [],
},
},
@@ -20,7 +20,7 @@ export const settingsDraftRoleFamilyState = createAtomFamilyState<
canAccessAllTools: false,
isEditable: false,
workspaceMembers: [],
permissionFlags: [],
permissionFlagGrants: [],
objectPermissions: [],
fieldPermissions: [],
rowLevelPermissionPredicates: [],
@@ -34,7 +34,7 @@ export const USER_QUERY_FRAGMENT = gql`
}
currentUserWorkspace {
id
permissionFlags
permissionFlagGrants
objectsPermissions {
...ObjectPermissionFragment
}
@@ -74,7 +74,8 @@ export const useLoadCurrentUser = () => {
if (isDefined(user.currentUserWorkspace)) {
setCurrentUserWorkspace({
permissionFlags: user.currentUserWorkspace.permissionFlags ?? [],
permissionFlagGrants:
user.currentUserWorkspace.permissionFlagGrants ?? [],
twoFactorAuthenticationMethodSummary:
user.currentUserWorkspace.twoFactorAuthenticationMethodSummary ?? [],
objectsPermissions:
@@ -1,4 +1,4 @@
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { useHasPermissionFlagGrant } from '@/settings/roles/hooks/useHasPermissionFlagGrant';
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
import {
ViewVisibility,
@@ -7,7 +7,9 @@ import {
export const useCanPersistViewChanges = () => {
const { currentView } = useGetCurrentViewOnly();
const hasViewsPermission = useHasPermissionFlag(PermissionFlagType.VIEWS);
const hasViewsPermission = useHasPermissionFlagGrant(
PermissionFlagType.VIEWS,
);
if (!currentView) {
return { canPersistChanges: false };
@@ -1,7 +1,7 @@
import { useEffect } from 'react';
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { useHasPermissionFlagGrant } from '@/settings/roles/hooks/useHasPermissionFlagGrant';
import { useAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentState';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState';
@@ -77,7 +77,7 @@ export const ViewPickerContentEffect = () => {
const { availableFieldsForGrouping } =
useGetAvailableFieldsToGroupRecordsBy();
const { availableFieldsForCalendar } = useGetAvailableFieldsForCalendar();
const hasViewPermission = useHasPermissionFlag(PermissionFlagType.VIEWS);
const hasViewPermission = useHasPermissionFlagGrant(PermissionFlagType.VIEWS);
useEffect(() => {
if (
@@ -1,6 +1,6 @@
import { useCreateManyNavigationMenuItems } from '@/navigation-menu-item/common/hooks/useCreateManyNavigationMenuItems';
import { useNavigationMenuItemsData } from '@/navigation-menu-item/display/hooks/useNavigationMenuItemsData';
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { useHasPermissionFlagGrant } from '@/settings/roles/hooks/useHasPermissionFlagGrant';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
@@ -53,7 +53,9 @@ export const ViewPickerOptionDropdown = ({
const setViewPickerReferenceViewId = useSetAtomComponentState(
viewPickerReferenceViewIdComponentState,
);
const hasViewsPermission = useHasPermissionFlag(PermissionFlagType.VIEWS);
const hasViewsPermission = useHasPermissionFlagGrant(
PermissionFlagType.VIEWS,
);
const { createManyNavigationMenuItems } = useCreateManyNavigationMenuItems();
const { navigationMenuItems, currentWorkspaceMemberId } =
@@ -6,21 +6,21 @@ import { WorkflowAiAgentPermissionsFlagRow } from './WorkflowAiAgentPermissionsF
type WorkflowAiAgentPermissionsFlagListProps = {
title: string;
permissions: SettingsRolePermissionsSettingPermission[];
enabledPermissionFlagKeys: PermissionFlagType[];
enabledPermissionFlagGrantKeys: PermissionFlagType[];
readonly: boolean;
showDeleteButton?: boolean;
onAddPermissionFlag?: (permissionKey: PermissionFlagType) => void;
onDeletePermissionFlag?: (permissionKey: PermissionFlagType) => void;
onAddPermissionFlagGrant?: (permissionKey: PermissionFlagType) => void;
onDeletePermissionFlagGrant?: (permissionKey: PermissionFlagType) => void;
};
export const WorkflowAiAgentPermissionsFlagList = ({
title,
permissions,
enabledPermissionFlagKeys,
enabledPermissionFlagGrantKeys,
readonly,
showDeleteButton = false,
onAddPermissionFlag,
onDeletePermissionFlag,
onAddPermissionFlagGrant,
onDeletePermissionFlagGrant,
}: WorkflowAiAgentPermissionsFlagListProps) => {
if (permissions.length === 0) {
return null;
@@ -29,7 +29,9 @@ export const WorkflowAiAgentPermissionsFlagList = ({
return (
<SidePanelGroup heading={title}>
{permissions.map((permission) => {
const isEnabled = enabledPermissionFlagKeys.includes(permission.key);
const isEnabled = enabledPermissionFlagGrantKeys.includes(
permission.key,
);
return (
<WorkflowAiAgentPermissionsFlagRow
@@ -38,8 +40,8 @@ export const WorkflowAiAgentPermissionsFlagList = ({
isEnabled={isEnabled}
readonly={readonly}
showDeleteButton={showDeleteButton}
onAdd={() => onAddPermissionFlag?.(permission.key)}
onDelete={() => onDeletePermissionFlag?.(permission.key)}
onAdd={() => onAddPermissionFlagGrant?.(permission.key)}
onDelete={() => onDeletePermissionFlagGrant?.(permission.key)}
/>
);
})}
@@ -1,5 +1,5 @@
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { useActionRolePermissionFlagConfig } from '@/settings/roles/role-permissions/permission-flags/hooks/useActionRolePermissionFlagConfig';
import { useActionRolePermissionFlagGrantConfig } from '@/settings/roles/role-permissions/permission-flags/hooks/useActionRolePermissionFlagGrantConfig';
import { SidePanelSubPageNavigationHeader } from '@/side-panel/pages/common/components/SidePanelSubPageNavigationHeader';
import { TextInput } from '@/ui/input/components/TextInput';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
@@ -98,12 +98,14 @@ export const WorkflowAiAgentPermissionsTab = ({
(item) => item.id === workflowAiAgentActionAgent?.roleId,
);
const objectPermissions = role?.objectPermissions || [];
const permissionFlagKeys =
role?.permissionFlags?.map((permissionFlag) => permissionFlag.flag) ?? [];
const permissionFlagGrantKeys =
role?.permissionFlagGrants?.map(
(permissionFlagGrant) => permissionFlagGrant.flag,
) ?? [];
const hasRoleWithPermissions =
isDefined(workflowAiAgentActionAgent?.roleId) &&
(objectPermissions.length > 0 || permissionFlagKeys.length > 0);
const actionPermissionsConfig = useActionRolePermissionFlagConfig({
(objectPermissions.length > 0 || permissionFlagGrantKeys.length > 0);
const actionPermissionsConfig = useActionRolePermissionFlagGrantConfig({
assignmentCapabilities: { canBeAssignedToAgents: true },
});
@@ -119,7 +121,7 @@ export const WorkflowAiAgentPermissionsTab = ({
filteredEnabledPermissions: filteredEnabledActionPermissions,
} = getFilteredPermissions({
permissions: actionPermissionsConfig,
permissionFlagKeys,
permissionFlagGrantKeys,
searchQuery,
});
@@ -134,12 +136,12 @@ export const WorkflowAiAgentPermissionsTab = ({
const {
handleAddPermission,
handleDeletePermission,
handleAddPermissionFlag,
handleDeletePermissionFlag,
handleAddPermissionFlagGrant,
handleDeletePermissionFlagGrant,
} = useWorkflowAiAgentPermissionActions({
readonly,
objectPermissions,
permissionFlagKeys,
permissionFlagGrantKeys,
refetchAgentAndRoles,
});
@@ -237,9 +239,9 @@ export const WorkflowAiAgentPermissionsTab = ({
<WorkflowAiAgentPermissionsFlagList
title={t`Actions`}
permissions={filteredActionPermissions}
enabledPermissionFlagKeys={permissionFlagKeys}
enabledPermissionFlagGrantKeys={permissionFlagGrantKeys}
readonly={readonly}
onAddPermissionFlag={handleAddPermissionFlag}
onAddPermissionFlagGrant={handleAddPermissionFlagGrant}
/>
</>
)}
@@ -255,10 +257,10 @@ export const WorkflowAiAgentPermissionsTab = ({
<WorkflowAiAgentPermissionsFlagList
title={t`Actions`}
permissions={filteredEnabledActionPermissions}
enabledPermissionFlagKeys={permissionFlagKeys}
enabledPermissionFlagGrantKeys={permissionFlagGrantKeys}
readonly={readonly}
showDeleteButton={!readonly}
onDeletePermissionFlag={handleDeletePermissionFlag}
onDeletePermissionFlagGrant={handleDeletePermissionFlagGrant}
/>
</>
)}
@@ -10,11 +10,11 @@ export const getFilteredPermissions = <
T extends { key: PermissionFlagType; name: string },
>({
permissions,
permissionFlagKeys,
permissionFlagGrantKeys,
searchQuery,
}: {
permissions: T[];
permissionFlagKeys: PermissionFlagType[];
permissionFlagGrantKeys: PermissionFlagType[];
searchQuery: string;
}): FilteredPermissionsResult<T> => {
const filteredPermissions = filterBySearchQuery<T>({
@@ -24,7 +24,7 @@ export const getFilteredPermissions = <
});
const enabledPermissions = permissions.filter((permission) =>
permissionFlagKeys.includes(permission.key),
permissionFlagGrantKeys.includes(permission.key),
);
const filteredEnabledPermissions = filterBySearchQuery<T>({
@@ -1,7 +1,7 @@
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { type SettingsRoleObjectPermissionKey } from '@/settings/roles/role-permissions/objects-permissions/constants/SettingsRoleObjectPermissionIconConfig';
import { useActionRolePermissionFlagConfig } from '@/settings/roles/role-permissions/permission-flags/hooks/useActionRolePermissionFlagConfig';
import { useSettingsRolePermissionFlagConfig } from '@/settings/roles/role-permissions/permission-flags/hooks/useSettingsRolePermissionFlagConfig';
import { useActionRolePermissionFlagGrantConfig } from '@/settings/roles/role-permissions/permission-flags/hooks/useActionRolePermissionFlagGrantConfig';
import { useSettingsRolePermissionFlagGrantConfig } from '@/settings/roles/role-permissions/permission-flags/hooks/useSettingsRolePermissionFlagGrantConfig';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { CRUD_PERMISSIONS } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/constants/WorkflowAiAgentCrudPermissions';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
@@ -20,20 +20,20 @@ import {
AssignRoleToAgentDocument,
CreateOneRoleDocument,
UpsertObjectPermissionsDocument,
UpsertPermissionFlagsDocument,
UpsertPermissionFlagGrantsDocument,
} from '~/generated-metadata/graphql';
type UseWorkflowAiAgentPermissionActionsParams = {
readonly: boolean;
objectPermissions: ObjectPermission[];
permissionFlagKeys: PermissionFlagType[];
permissionFlagGrantKeys: PermissionFlagType[];
refetchAgentAndRoles: () => Promise<{ refetchedAgent?: Agent }>;
};
export const useWorkflowAiAgentPermissionActions = ({
readonly,
objectPermissions,
permissionFlagKeys,
permissionFlagGrantKeys,
refetchAgentAndRoles,
}: UseWorkflowAiAgentPermissionActionsParams) => {
const { enqueueSuccessSnackBar } = useSnackBar();
@@ -41,10 +41,10 @@ export const useWorkflowAiAgentPermissionActions = ({
useAtomState(workflowAiAgentActionAgentState);
const { alphaSortedActiveNonSystemObjectMetadataItems: objectMetadataItems } =
useFilteredObjectMetadataItems();
const settingsPermissionsConfig = useSettingsRolePermissionFlagConfig({
const settingsPermissionsConfig = useSettingsRolePermissionFlagGrantConfig({
assignmentCapabilities: { canBeAssignedToAgents: true },
});
const actionPermissionsConfig = useActionRolePermissionFlagConfig({
const actionPermissionsConfig = useActionRolePermissionFlagGrantConfig({
assignmentCapabilities: { canBeAssignedToAgents: true },
});
@@ -60,11 +60,13 @@ export const useWorkflowAiAgentPermissionActions = ({
const [upsertObjectPermissions] = useMutation(
UpsertObjectPermissionsDocument,
);
const [upsertPermissionFlags] = useMutation(UpsertPermissionFlagsDocument);
const [upsertPermissionFlagGrants] = useMutation(
UpsertPermissionFlagGrantsDocument,
);
const roleId = workflowAiAgentActionAgent?.roleId;
const permissionFlagLabelMap = useMemo(
const permissionFlagGrantLabelMap = useMemo(
() =>
[...settingsPermissionsConfig, ...actionPermissionsConfig].reduce<
Partial<Record<PermissionFlagType, string>>
@@ -296,8 +298,8 @@ export const useWorkflowAiAgentPermissionActions = ({
}
};
const handleAddPermissionFlag = async (
permissionFlagKey: PermissionFlagType,
const handleAddPermissionFlagGrant = async (
permissionFlagGrantKey: PermissionFlagType,
) => {
if (readonly) {
return;
@@ -305,15 +307,21 @@ export const useWorkflowAiAgentPermissionActions = ({
const ensuredRoleId = await ensureRoleId();
if (!ensuredRoleId || permissionFlagKeys.includes(permissionFlagKey)) {
if (
!ensuredRoleId ||
permissionFlagGrantKeys.includes(permissionFlagGrantKey)
) {
return;
}
await upsertPermissionFlags({
await upsertPermissionFlagGrants({
variables: {
upsertPermissionFlagsInput: {
upsertPermissionFlagGrantsInput: {
roleId: ensuredRoleId,
permissionFlagKeys: [...permissionFlagKeys, permissionFlagKey],
permissionFlagGrantKeys: [
...permissionFlagGrantKeys,
permissionFlagGrantKey,
],
},
},
});
@@ -323,33 +331,33 @@ export const useWorkflowAiAgentPermissionActions = ({
setWorkflowAiAgentPermissionsSelectedObjectId(undefined);
};
const handleDeletePermissionFlag = async (
permissionFlagKey: PermissionFlagType,
const handleDeletePermissionFlagGrant = async (
permissionFlagGrantKey: PermissionFlagType,
) => {
if (!isDefined(roleId) || readonly) {
return;
}
if (!permissionFlagKeys.includes(permissionFlagKey)) {
if (!permissionFlagGrantKeys.includes(permissionFlagGrantKey)) {
return;
}
const permissionFlagKeysAfterRemoval = permissionFlagKeys.filter(
(flag) => flag !== permissionFlagKey,
const permissionFlagGrantKeysAfterRemoval = permissionFlagGrantKeys.filter(
(flag) => flag !== permissionFlagGrantKey,
);
await upsertPermissionFlags({
await upsertPermissionFlagGrants({
variables: {
upsertPermissionFlagsInput: {
upsertPermissionFlagGrantsInput: {
roleId,
permissionFlagKeys: permissionFlagKeysAfterRemoval,
permissionFlagGrantKeys: permissionFlagGrantKeysAfterRemoval,
},
},
});
await refetchAgentAndRoles();
const permissionLabel = permissionFlagLabelMap[permissionFlagKey];
const permissionLabel = permissionFlagGrantLabelMap[permissionFlagGrantKey];
if (isDefined(permissionLabel)) {
enqueueSuccessSnackBar({
@@ -361,7 +369,7 @@ export const useWorkflowAiAgentPermissionActions = ({
return {
handleAddPermission,
handleDeletePermission,
handleAddPermissionFlag,
handleDeletePermissionFlag,
handleAddPermissionFlagGrant,
handleDeletePermissionFlagGrant,
};
};
@@ -130,7 +130,7 @@ export const SettingsAgentRoleTab = ({
apiKeys: [],
objectPermissions: [],
fieldPermissions: [],
permissionFlags: [],
permissionFlagGrants: [],
rowLevelPermissionPredicateGroups: [],
rowLevelPermissionPredicates: [],
});
@@ -6,7 +6,7 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { useUpgradeApplication } from '@/marketplace/hooks/useUpgradeApplication';
import { objectMetadataItemsSelector } from '@/object-metadata/states/objectMetadataItemsSelector';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { useHasPermissionFlagGrant } from '@/settings/roles/hooks/useHasPermissionFlagGrant';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { TabList } from '@/ui/layout/tab-list/components/TabList';
@@ -112,7 +112,7 @@ export const SettingsApplicationDetails = () => {
const { upgrade, isUpgrading } = useUpgradeApplication();
const canInstallMarketplaceApps = useHasPermissionFlag(
const canInstallMarketplaceApps = useHasPermissionFlagGrant(
PermissionFlagType.MARKETPLACE_APPS,
);
@@ -1,4 +1,4 @@
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { useHasPermissionFlagGrant } from '@/settings/roles/hooks/useHasPermissionFlagGrant';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { TabList } from '@/ui/layout/tab-list/components/TabList';
@@ -22,7 +22,7 @@ const APPLICATIONS_TAB_LIST_ID = 'applications-tab-list';
export const SettingsApplications = () => {
const { t } = useLingui();
const hasDeveloperAccess = useHasPermissionFlag(
const hasDeveloperAccess = useHasPermissionFlagGrant(
PermissionFlagType.API_KEYS_AND_WEBHOOKS,
);
@@ -2,7 +2,7 @@ import { CurrentApplicationContext } from '@/applications/contexts/CurrentApplic
import { useInstallMarketplaceApp } from '@/marketplace/hooks/useInstallMarketplaceApp';
import { useUpgradeApplication } from '@/marketplace/hooks/useUpgradeApplication';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { useHasPermissionFlagGrant } from '@/settings/roles/hooks/useHasPermissionFlagGrant';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { TabList } from '@/ui/layout/tab-list/components/TabList';
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
@@ -46,7 +46,7 @@ export const SettingsAvailableApplicationDetails = () => {
const { install, isInstalling } = useInstallMarketplaceApp();
const { upgrade, isUpgrading } = useUpgradeApplication();
const canInstallMarketplaceApps = useHasPermissionFlag(
const canInstallMarketplaceApps = useHasPermissionFlagGrant(
PermissionFlagType.MARKETPLACE_APPS,
);
@@ -121,12 +121,12 @@ const buildSyntheticRole = (
canReadFieldValue: permission.canReadFieldValue,
canUpdateFieldValue: permission.canUpdateFieldValue,
})),
permissionFlags: (defaultRole.permissionFlags ?? []).map(
(permissionFlag) => ({
__typename: 'PermissionFlag' as const,
permissionFlagGrants: (defaultRole.permissionFlagGrants ?? []).map(
(permissionFlagGrant) => ({
__typename: 'PermissionFlagGrant' as const,
id: uuidv4(),
roleId: defaultRole.universalIdentifier,
flag: permissionFlag.flag,
flag: permissionFlagGrant.flag,
}),
),
});
@@ -6,7 +6,7 @@ import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
import { useImpersonationSession } from '@/auth/hooks/useImpersonationSession';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsRolesQueryEffect } from '@/settings/roles/components/SettingsRolesQueryEffect';
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { useHasPermissionFlagGrant } from '@/settings/roles/hooks/useHasPermissionFlagGrant';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
@@ -54,7 +54,8 @@ export const SettingsWorkspaceMember = () => {
const [impersonate] = useMutation(ImpersonateDocument);
const isImpersonating = useAtomStateValue(isImpersonatingState);
const canImpersonate =
useHasPermissionFlag(PermissionFlagType.IMPERSONATE) && !isImpersonating;
useHasPermissionFlagGrant(PermissionFlagType.IMPERSONATE) &&
!isImpersonating;
const {
roles,
@@ -3,7 +3,7 @@ import { SettingsPath } from 'twenty-shared/types';
import { getSettingsPath } from 'twenty-shared/utils';
import { IconLock, IconUserPlus, IconUsers } from 'twenty-ui/display';
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { useHasPermissionFlagGrant } from '@/settings/roles/hooks/useHasPermissionFlagGrant';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { TabList } from '@/ui/layout/tab-list/components/TabList';
@@ -23,7 +23,9 @@ const MEMBERS_TAB_ROLES_ID = 'roles';
export const SettingsWorkspaceMembers = () => {
const { t } = useLingui();
const hasRolesPermission = useHasPermissionFlag(PermissionFlagType.ROLES);
const hasRolesPermission = useHasPermissionFlagGrant(
PermissionFlagType.ROLES,
);
const activeTabId = useAtomComponentStateValue(
activeTabIdComponentState,
@@ -743,7 +743,7 @@ export const mockedMinimalMetadata: MinimalMetadata =
"hash": "b022456a-bd5b-4314-9fb5-0111c80a8649"
},
{
"collectionName": "permissionFlag",
"collectionName": "permissionFlagGrant",
"hash": "2de6c5b6-3b5c-4f61-8426-92c3d4c5daac"
},
{
@@ -23,7 +23,7 @@ export const mockedRoles: Role[] =
"canBeAssignedToUsers": true,
"canBeAssignedToAgents": false,
"canBeAssignedToApiKeys": true,
"permissionFlags": [],
"permissionFlagGrants": [],
"objectPermissions": [],
"fieldPermissions": [],
"rowLevelPermissionPredicates": [],
@@ -68,7 +68,7 @@ export const mockedRoles: Role[] =
"canBeAssignedToUsers": true,
"canBeAssignedToAgents": false,
"canBeAssignedToApiKeys": false,
"permissionFlags": [],
"permissionFlagGrants": [],
"objectPermissions": [],
"fieldPermissions": [],
"rowLevelPermissionPredicates": [],
@@ -105,7 +105,7 @@ export const mockedRoles: Role[] =
"canBeAssignedToUsers": true,
"canBeAssignedToAgents": true,
"canBeAssignedToApiKeys": true,
"permissionFlags": [],
"permissionFlagGrants": [],
"objectPermissions": [
{
"__typename": "ObjectPermission",
@@ -184,7 +184,7 @@ export const mockedRoles: Role[] =
"canBeAssignedToUsers": true,
"canBeAssignedToAgents": false,
"canBeAssignedToApiKeys": false,
"permissionFlags": [],
"permissionFlagGrants": [],
"objectPermissions": [],
"fieldPermissions": [],
"rowLevelPermissionPredicates": [],
@@ -199,7 +199,7 @@ export const mockedUserData: MockedUser = {
workspaceMember: mockedWorkspaceMemberData,
currentWorkspace: mockCurrentWorkspace,
currentUserWorkspace: {
permissionFlags: [
permissionFlagGrants: [
PermissionFlagType.WORKSPACE_MEMBERS,
PermissionFlagType.CONNECTED_ACCOUNTS,
],
@@ -269,7 +269,7 @@ export const mockedOnboardingUserData = (
workspaceMembers: [],
currentWorkspace: mockCurrentWorkspace,
currentUserWorkspace: {
permissionFlags: [PermissionFlagType.WORKSPACE_MEMBERS],
permissionFlagGrants: [PermissionFlagType.WORKSPACE_MEMBERS],
objectPermissions: getTestEnrichedObjectMetadataItemsMock().map(
(item) => ({
objectMetadataId: item.id,
@@ -330,7 +330,7 @@ export const EXPECTED_MANIFEST: Manifest = {
canBeAssignedToApiKeys: false,
fieldPermissions: [],
objectPermissions: [],
permissionFlags: [],
permissionFlagGrants: [],
},
],
};
@@ -1385,7 +1385,7 @@ export const EXPECTED_MANIFEST: Manifest = {
universalIdentifier: 'c0c1c2c3-c4c5-4000-8000-000000000001',
fieldPermissions: [],
objectPermissions: [],
permissionFlags: [],
permissionFlagGrants: [],
},
{
canBeAssignedToAgents: false,
@@ -1417,7 +1417,7 @@ export const EXPECTED_MANIFEST: Manifest = {
objectUniversalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05',
},
],
permissionFlags: [
permissionFlagGrants: [
{
universalIdentifier: '01d7865a-7700-5d49-b2aa-62623c2cbac7',
flag: PermissionFlagType.APPLICATIONS,
@@ -36,7 +36,7 @@ const buildMockCommandMenuContextApi = (
},
selectedRecords: [],
featureFlags: {},
permissionFlags: {},
permissionFlagGrants: {},
targetObjectReadPermissions: {},
targetObjectWritePermissions: {},
objectMetadataItem: {},
@@ -13,7 +13,7 @@ exports[`stub-twenty-sdk-define plugin > matches the recorded export partition 1
"ObjectRecordGroupByDateGranularity",
"OnDeleteAction",
"PageLayoutTabLayoutMode",
"PermissionFlag",
"PermissionFlagGrant",
"RelationType",
"STANDARD_OBJECT",
"STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS",
@@ -28,13 +28,13 @@ export const fromRoleConfigToRoleManifest = (
),
}),
),
permissionFlags: (roleConfig.permissionFlags ?? []).map(
(permissionFlag) => ({
permissionFlagGrants: (roleConfig.permissionFlagGrants ?? []).map(
(permissionFlagGrant) => ({
universalIdentifier: uuidv5(
`${roleConfig.universalIdentifier}:${permissionFlag}`,
`${roleConfig.universalIdentifier}:${permissionFlagGrant}`,
ROLE_UNIVERSAL_IDENTIFIER_NAMESPACE,
),
flag: permissionFlag,
flag: permissionFlagGrant,
}),
),
};
+1 -1
View File
@@ -86,7 +86,7 @@ export type {
export { defineApplicationRole } from '@/sdk/define/roles/define-application-role';
export { defineRole } from '@/sdk/define/roles/define-role';
export { PermissionFlag } from '@/sdk/define/roles/permission-flag-type';
export { PermissionFlagGrant } from '@/sdk/define/roles/permission-flag-type';
export { defineSkill } from '@/sdk/define/skills/define-skill';
@@ -32,16 +32,16 @@ describe('defineApplicationRole', () => {
expect(result.config?.canReadAllObjectRecords).toBe(true);
});
it('should accept permissionFlags', () => {
it('should accept permissionFlagGrants', () => {
const config = {
...validConfig,
permissionFlags: ['UPLOAD_FILE'],
permissionFlagGrants: ['UPLOAD_FILE'],
};
const result = defineApplicationRole(config as any);
expect(result.success).toBe(true);
expect(result.config?.permissionFlags).toHaveLength(1);
expect(result.config?.permissionFlagGrants).toHaveLength(1);
});
it('should return error when universalIdentifier is missing', () => {
@@ -32,16 +32,16 @@ describe('defineRole', () => {
expect(result.config?.canReadAllObjectRecords).toBe(true);
});
it('should accept permissionFlags', () => {
it('should accept permissionFlagGrants', () => {
const config = {
...validConfig,
permissionFlags: ['UPLOAD_FILE', 'DOWNLOAD_FILE'],
permissionFlagGrants: ['UPLOAD_FILE', 'DOWNLOAD_FILE'],
};
const result = defineRole(config as any);
expect(result.success).toBe(true);
expect(result.config?.permissionFlags).toHaveLength(2);
expect(result.config?.permissionFlagGrants).toHaveLength(2);
});
it('should return error when universalIdentifier is missing', () => {
@@ -1 +1 @@
export { PermissionFlagType as PermissionFlag } from 'twenty-shared/constants';
export { PermissionFlagType as PermissionFlagGrant } from 'twenty-shared/constants';
@@ -7,9 +7,9 @@ import { type PermissionFlagType } from 'twenty-shared/constants';
export type RoleConfig = Omit<
RoleManifest,
'objectPermissions' | 'fieldPermissions' | 'permissionFlags'
'objectPermissions' | 'fieldPermissions' | 'permissionFlagGrants'
> & {
objectPermissions?: Omit<ObjectPermissionManifest, 'universalIdentifier'>[];
fieldPermissions?: Omit<FieldPermissionManifest, 'universalIdentifier'>[];
permissionFlags?: PermissionFlagType[];
permissionFlagGrants?: PermissionFlagType[];
};
@@ -33,6 +33,7 @@ import { ObjectPermissionEntity } from 'src/engine/metadata-modules/object-permi
import { PageLayoutTabEntity } from 'src/engine/metadata-modules/page-layout-tab/entities/page-layout-tab.entity';
import { PageLayoutWidgetEntity } from 'src/engine/metadata-modules/page-layout-widget/entities/page-layout-widget.entity';
import { PageLayoutEntity } from 'src/engine/metadata-modules/page-layout/entities/page-layout.entity';
import { PermissionFlagGrantEntity } from 'src/engine/metadata-modules/permission-flag-grant/permission-flag-grant.entity';
import { PermissionFlagEntity } from 'src/engine/metadata-modules/permission-flag/permission-flag.entity';
import { RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.entity';
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
@@ -67,7 +68,7 @@ const WORKSPACE_RELATED_ENTITIES: EntityTarget<ObjectLiteral>[] = [
ViewFilterGroupEntity,
FieldPermissionEntity,
ObjectPermissionEntity,
PermissionFlagEntity,
PermissionFlagGrantEntity,
RoleTargetEntity,
SearchFieldMetadataEntity,
RowLevelPermissionPredicateEntity,
@@ -82,6 +83,7 @@ const WORKSPACE_RELATED_ENTITIES: EntityTarget<ObjectLiteral>[] = [
// Level 2: Children that depend on core entities
FieldMetadataEntity,
PageLayoutEntity,
PermissionFlagEntity,
SkillEntity,
LogicFunctionEntity,
@@ -0,0 +1,92 @@
import { QueryRunner } from 'typeorm';
import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator';
import { FastInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/fast-instance-command.interface';
@RegisteredInstanceCommand('2.5.0', 1778235340020)
export class RenamePermissionFlagToPermissionFlagGrantFastInstanceCommand
implements FastInstanceCommand
{
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "core"."permissionFlag" RENAME TO "permissionFlagGrant"`,
);
await queryRunner.query(
`ALTER TABLE "core"."permissionFlagGrant" RENAME CONSTRAINT "IDX_PERMISSION_FLAG_FLAG_ROLE_ID_UNIQUE" TO "IDX_PERMISSION_FLAG_GRANT_FLAG_ROLE_ID_UNIQUE"`,
);
await queryRunner.query(
`ALTER INDEX "core"."IDX_PERMISSION_FLAG_ROLE_ID" RENAME TO "IDX_PERMISSION_FLAG_GRANT_ROLE_ID"`,
);
// Re-hash inherited constraints/indexes so TypeORM's schema diff matches
// the renamed table. Original names were derived from "permissionFlag"
// and stay free for the new catalog table created by the next migration.
await queryRunner.query(
`ALTER TABLE "core"."permissionFlagGrant" DROP CONSTRAINT "FK_13f8ca9c517976733a1ce4c10eb"`,
);
await queryRunner.query(
`ALTER TABLE "core"."permissionFlagGrant" DROP CONSTRAINT "FK_835bc9f7ef959debfc5cd268049"`,
);
await queryRunner.query(
`ALTER TABLE "core"."permissionFlagGrant" DROP CONSTRAINT "FK_b26a9d39a88d0e72373c677c6c5"`,
);
await queryRunner.query(
`DROP INDEX "core"."IDX_da8ffd3c24b4a819430a861067"`,
);
await queryRunner.query(
`CREATE UNIQUE INDEX "IDX_49d4aa416a11933e35263390ad" ON "core"."permissionFlagGrant" ("workspaceId", "universalIdentifier")`,
);
await queryRunner.query(
`ALTER TABLE "core"."permissionFlagGrant" ADD CONSTRAINT "FK_272c63f634c6b9364c8ab0e94c9" FOREIGN KEY ("workspaceId") REFERENCES "core"."workspace"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "core"."permissionFlagGrant" ADD CONSTRAINT "FK_6090c1bb1c94b4e7260acb4d435" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "core"."permissionFlagGrant" ADD CONSTRAINT "FK_d19956ecbc979cf2e67df6fbd50" FOREIGN KEY ("roleId") REFERENCES "core"."role"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "core"."permissionFlagGrant" DROP CONSTRAINT "FK_d19956ecbc979cf2e67df6fbd50"`,
);
await queryRunner.query(
`ALTER TABLE "core"."permissionFlagGrant" DROP CONSTRAINT "FK_6090c1bb1c94b4e7260acb4d435"`,
);
await queryRunner.query(
`ALTER TABLE "core"."permissionFlagGrant" DROP CONSTRAINT "FK_272c63f634c6b9364c8ab0e94c9"`,
);
await queryRunner.query(
`DROP INDEX "core"."IDX_49d4aa416a11933e35263390ad"`,
);
await queryRunner.query(
`CREATE UNIQUE INDEX "IDX_da8ffd3c24b4a819430a861067" ON "core"."permissionFlagGrant" ("workspaceId", "universalIdentifier")`,
);
await queryRunner.query(
`ALTER TABLE "core"."permissionFlagGrant" ADD CONSTRAINT "FK_b26a9d39a88d0e72373c677c6c5" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "core"."permissionFlagGrant" ADD CONSTRAINT "FK_835bc9f7ef959debfc5cd268049" FOREIGN KEY ("workspaceId") REFERENCES "core"."workspace"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "core"."permissionFlagGrant" ADD CONSTRAINT "FK_13f8ca9c517976733a1ce4c10eb" FOREIGN KEY ("roleId") REFERENCES "core"."role"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER INDEX "core"."IDX_PERMISSION_FLAG_GRANT_ROLE_ID" RENAME TO "IDX_PERMISSION_FLAG_ROLE_ID"`,
);
await queryRunner.query(
`ALTER TABLE "core"."permissionFlagGrant" RENAME CONSTRAINT "IDX_PERMISSION_FLAG_GRANT_FLAG_ROLE_ID_UNIQUE" TO "IDX_PERMISSION_FLAG_FLAG_ROLE_ID_UNIQUE"`,
);
await queryRunner.query(
`ALTER TABLE "core"."permissionFlagGrant" RENAME TO "permissionFlag"`,
);
}
}
@@ -0,0 +1,73 @@
import { QueryRunner } from 'typeorm';
import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator';
import { FastInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/fast-instance-command.interface';
@RegisteredInstanceCommand('2.5.0', 1778235340021)
export class PermissionFlagSyncableEntityFastInstanceCommand
implements FastInstanceCommand
{
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE "core"."permissionFlag" (
"id" uuid NOT NULL DEFAULT uuid_generate_v4(),
"workspaceId" uuid NOT NULL,
"applicationId" uuid NOT NULL,
"universalIdentifier" uuid NOT NULL,
"key" varchar NOT NULL,
"label" varchar NOT NULL,
"description" text,
"iconKey" varchar,
"permissionType" varchar NOT NULL,
"isRelevantForAgents" boolean NOT NULL DEFAULT false,
"isRelevantForUsers" boolean NOT NULL DEFAULT false,
"isRelevantForApiKeys" boolean NOT NULL DEFAULT false,
"isCustom" boolean NOT NULL DEFAULT false,
"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
"updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
CONSTRAINT "IDX_PERMISSION_FLAG_KEY_WORKSPACE_ID_UNIQUE" UNIQUE ("key", "workspaceId"),
CONSTRAINT "PK_permissionFlag_id" PRIMARY KEY ("id")
)`,
);
await queryRunner.query(
`CREATE INDEX "IDX_PERMISSION_FLAG_APPLICATION_ID" ON "core"."permissionFlag" ("applicationId")`,
);
// Reuses canonical hash names freed by the rename migration so TypeORM's
// schema diff matches without further intervention.
await queryRunner.query(
`CREATE UNIQUE INDEX "IDX_da8ffd3c24b4a819430a861067" ON "core"."permissionFlag" ("workspaceId", "universalIdentifier")`,
);
await queryRunner.query(
`ALTER TABLE "core"."permissionFlag"
ADD CONSTRAINT "FK_835bc9f7ef959debfc5cd268049"
FOREIGN KEY ("workspaceId") REFERENCES "core"."workspace"("id")
ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "core"."permissionFlag"
ADD CONSTRAINT "FK_b26a9d39a88d0e72373c677c6c5"
FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id")
ON DELETE CASCADE ON UPDATE NO ACTION`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "core"."permissionFlag" DROP CONSTRAINT IF EXISTS "FK_b26a9d39a88d0e72373c677c6c5"`,
);
await queryRunner.query(
`ALTER TABLE "core"."permissionFlag" DROP CONSTRAINT IF EXISTS "FK_835bc9f7ef959debfc5cd268049"`,
);
await queryRunner.query(
`DROP INDEX IF EXISTS "core"."IDX_da8ffd3c24b4a819430a861067"`,
);
await queryRunner.query(
`DROP INDEX IF EXISTS "core"."IDX_PERMISSION_FLAG_APPLICATION_ID"`,
);
await queryRunner.query(`DROP TABLE IF EXISTS "core"."permissionFlag"`);
}
}
@@ -29,6 +29,8 @@ import { AddToolAndWorkflowActionTriggerSettingsFastInstanceCommand } from 'src/
import { BackfillApplicationVariableUniversalIdentifierSlowInstanceCommand } from 'src/database/commands/upgrade-version-command/2-3/2-3-instance-command-slow-1777966965588-backfill-application-variable-universal-identifier';
import { MigrateToolTriggerSettingsSlowInstanceCommand } from 'src/database/commands/upgrade-version-command/2-3/2-3-instance-command-slow-1797000002000-migrate-tool-trigger-settings';
import { AddMetadataToBillingPriceFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-4/2-4-instance-command-fast-1777100000000-add-metadata-to-billing-price';
import { RenamePermissionFlagToPermissionFlagGrantFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-5/2-5-instance-command-fast-1778235340020-rename-permission-flag-to-permission-flag-grant';
import { PermissionFlagSyncableEntityFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-5/2-5-instance-command-fast-1778235340021-permission-flag-syncable-entity';
import { AddEmailGroupChannelTypeFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-4/2-4-instance-command-fast-1778256809018-add-email-group-channel-type';
import { AddApplicationIdToPublicDomainFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-4/2-4-instance-command-fast-1798000003000-add-application-id-to-public-domain';
import { AddIsInternalMessagesImportEnabledFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-5/2-5-instance-command-fast-1778525104406-add-is-internal-messages-import-enabled';
@@ -64,6 +66,8 @@ export const INSTANCE_COMMANDS = [
RemoveUserDefaultAvatarUrlFastInstanceCommand,
TransformApplicationVariableToSyncableEntityFastInstanceCommand,
BackfillApplicationVariableUniversalIdentifierSlowInstanceCommand,
RenamePermissionFlagToPermissionFlagGrantFastInstanceCommand,
PermissionFlagSyncableEntityFastInstanceCommand,
AddEmailGroupChannelTypeFastInstanceCommand,
AddApplicationIdToPublicDomainFastInstanceCommand,
AddIsInternalMessagesImportEnabledFastInstanceCommand,
@@ -0,0 +1,23 @@
import { type UniversalFlatPermissionFlagGrant } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-permission-flag-grant.type';
import { PermissionFlagGrantManifest } from 'twenty-shared/application';
export const fromPermissionFlagGrantToUniversalFlatPermissionFlagGrant = ({
permissionFlagGrant,
roleUniversalIdentifier,
applicationUniversalIdentifier,
now,
}: {
permissionFlagGrant: PermissionFlagGrantManifest;
roleUniversalIdentifier: string;
applicationUniversalIdentifier: string;
now: string;
}): UniversalFlatPermissionFlagGrant => {
return {
universalIdentifier: permissionFlagGrant.universalIdentifier,
applicationUniversalIdentifier,
roleUniversalIdentifier,
flag: permissionFlagGrant.flag,
createdAt: now,
updatedAt: now,
};
};
@@ -1,23 +0,0 @@
import { type UniversalFlatPermissionFlag } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-permission-flag.type';
import { PermissionFlagManifest } from 'twenty-shared/application';
export const fromPermissionFlagToUniversalFlatPermissionFlag = ({
permissionFlag,
roleUniversalIdentifier,
applicationUniversalIdentifier,
now,
}: {
permissionFlag: PermissionFlagManifest;
roleUniversalIdentifier: string;
applicationUniversalIdentifier: string;
now: string;
}): UniversalFlatPermissionFlag => {
return {
universalIdentifier: permissionFlag.universalIdentifier,
applicationUniversalIdentifier,
roleUniversalIdentifier,
flag: permissionFlag.flag,
createdAt: now,
updatedAt: now,
};
};
@@ -32,7 +32,7 @@ export const fromRoleManifestToUniversalFlatRole = ({
roleTargetUniversalIdentifiers: [],
objectPermissionUniversalIdentifiers: [],
fieldPermissionUniversalIdentifiers: [],
permissionFlagUniversalIdentifiers: [],
permissionFlagGrantUniversalIdentifiers: [],
rowLevelPermissionPredicateUniversalIdentifiers: [],
rowLevelPermissionPredicateGroupUniversalIdentifiers: [],
createdAt: now,
@@ -17,7 +17,7 @@ import { fromObjectPermissionManifestToUniversalFlatObjectPermission } from 'src
import { fromPageLayoutManifestToUniversalFlatPageLayout } from 'src/engine/core-modules/application/application-manifest/converters/from-page-layout-manifest-to-universal-flat-page-layout.util';
import { fromPageLayoutTabManifestToUniversalFlatPageLayoutTab } from 'src/engine/core-modules/application/application-manifest/converters/from-page-layout-tab-manifest-to-universal-flat-page-layout-tab.util';
import { fromPageLayoutWidgetManifestToUniversalFlatPageLayoutWidget } from 'src/engine/core-modules/application/application-manifest/converters/from-page-layout-widget-manifest-to-universal-flat-page-layout-widget.util';
import { fromPermissionFlagToUniversalFlatPermissionFlag } from 'src/engine/core-modules/application/application-manifest/converters/from-permission-flag-to-universal-flat-permission-flag.util';
import { fromPermissionFlagGrantToUniversalFlatPermissionFlagGrant } from 'src/engine/core-modules/application/application-manifest/converters/from-permission-flag-grant-to-universal-flat-permission-flag-grant.util';
import { fromRoleManifestToUniversalFlatRole } from 'src/engine/core-modules/application/application-manifest/converters/from-role-manifest-to-universal-flat-role.util';
import { fromSkillManifestToUniversalFlatSkill } from 'src/engine/core-modules/application/application-manifest/converters/from-skill-manifest-to-universal-flat-skill.util';
import { computeSearchVectorUniversalSettingsFromObjectManifest } from 'src/engine/core-modules/application/application-manifest/utils/compute-search-vector-universal-settings-from-object-manifest.util';
@@ -212,16 +212,17 @@ export const computeApplicationManifestAllUniversalFlatEntityMaps = ({
});
}
for (const permissionFlag of roleManifest.permissionFlags ?? []) {
for (const permissionFlagGrant of roleManifest.permissionFlagGrants ?? []) {
addUniversalFlatEntityToUniversalFlatEntityMapsThroughMutationOrThrow({
universalFlatEntity: fromPermissionFlagToUniversalFlatPermissionFlag({
permissionFlag,
roleUniversalIdentifier: roleManifest.universalIdentifier,
applicationUniversalIdentifier,
now,
}),
universalFlatEntity:
fromPermissionFlagGrantToUniversalFlatPermissionFlagGrant({
permissionFlagGrant,
roleUniversalIdentifier: roleManifest.universalIdentifier,
applicationUniversalIdentifier,
now,
}),
universalFlatEntityMapsToMutate:
allUniversalFlatEntityMaps.flatPermissionFlagMaps,
allUniversalFlatEntityMaps.flatPermissionFlagGrantMaps,
});
}
}
@@ -4,7 +4,7 @@ export const USER_WORKSPACE_ENTITY_NON_CACHED_PROPERTIES = [
'workspace',
'user',
'twoFactorAuthenticationMethods',
'permissionFlags',
'permissionFlagGrants',
'objectPermissions',
'objectsPermissions',
'twoFactorAuthenticationMethodSummary',

Some files were not shown because too many files have changed in this diff Show More