diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql index 9a98119cd9..244410391d 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql +++ b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql @@ -247,7 +247,7 @@ type FieldPermission { type RolePermissionFlag { id: UUID! roleId: UUID! - flag: PermissionFlagType! + flag: String! } type ApiKeyForRole { @@ -4029,7 +4029,7 @@ input ObjectPermissionInput { input UpsertPermissionFlagsInput { roleId: UUID! - permissionFlagKeys: [PermissionFlagType!]! + permissionFlagKeys: [String!]! } input UpsertFieldPermissionsInput { diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.ts b/packages/twenty-client-sdk/src/metadata/generated/schema.ts index 783c62ac70..c95c173a8e 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/schema.ts +++ b/packages/twenty-client-sdk/src/metadata/generated/schema.ts @@ -193,7 +193,7 @@ export interface FieldPermission { export interface RolePermissionFlag { id: Scalars['UUID'] roleId: Scalars['UUID'] - flag: PermissionFlagType + flag: Scalars['String'] __typename: 'RolePermissionFlag' } @@ -6160,7 +6160,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 UpsertPermissionFlagsInput {roleId: Scalars['UUID'],permissionFlagKeys: Scalars['String'][]} export interface UpsertFieldPermissionsInput {roleId: Scalars['UUID'],fieldPermissions: FieldPermissionInput[]} diff --git a/packages/twenty-client-sdk/src/metadata/generated/types.ts b/packages/twenty-client-sdk/src/metadata/generated/types.ts index d51d934ab2..3bc3a63046 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/types.ts +++ b/packages/twenty-client-sdk/src/metadata/generated/types.ts @@ -508,7 +508,7 @@ export default { 3 ], "flag": [ - 18 + 1 ], "__typename": [ 1 @@ -10279,7 +10279,7 @@ export default { 3 ], "permissionFlagKeys": [ - 18 + 1 ], "__typename": [ 1 diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index d46da6dc07..edb09c54c5 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -4651,7 +4651,7 @@ export type Role = { export type RolePermissionFlag = { __typename?: 'RolePermissionFlag'; - flag: PermissionFlagType; + flag: Scalars['String']; id: Scalars['UUID']; roleId: Scalars['UUID']; }; @@ -5424,7 +5424,7 @@ export type UpsertObjectPermissionsInput = { }; export type UpsertPermissionFlagsInput = { - permissionFlagKeys: Array; + permissionFlagKeys: Array; roleId: Scalars['UUID']; }; @@ -7347,7 +7347,7 @@ export type ObjectPermissionFragmentFragment = { __typename?: 'ObjectPermission' export type RoleFragmentFragment = { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, canAccessAllTools: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean, canBeAssignedToUsers: boolean, canBeAssignedToAgents: boolean, canBeAssignedToApiKeys: boolean }; -export type RolePermissionFlagFragmentFragment = { __typename?: 'RolePermissionFlag', id: string, flag: PermissionFlagType, roleId: string }; +export type RolePermissionFlagFragmentFragment = { __typename?: 'RolePermissionFlag', id: string, flag: string, roleId: string }; export type RowLevelPermissionPredicateFragmentFragment = { __typename?: 'RowLevelPermissionPredicate', id: string, fieldMetadataId: string, objectMetadataId: string, operand: RowLevelPermissionPredicateOperand, subFieldName?: string | null, workspaceMemberFieldMetadataId?: string | null, workspaceMemberSubFieldName?: string | null, rowLevelPermissionPredicateGroupId?: string | null, positionInRowLevelPermissionPredicateGroup?: number | null, roleId: string, value?: any | null }; @@ -7401,7 +7401,7 @@ export type UpsertPermissionFlagsMutationVariables = Exact<{ }>; -export type UpsertPermissionFlagsMutation = { __typename?: 'Mutation', upsertPermissionFlags: Array<{ __typename?: 'RolePermissionFlag', id: string, flag: PermissionFlagType, roleId: string }> }; +export type UpsertPermissionFlagsMutation = { __typename?: 'Mutation', upsertPermissionFlags: Array<{ __typename?: 'RolePermissionFlag', id: string, flag: string, roleId: string }> }; export type UpsertRowLevelPermissionPredicatesMutationVariables = Exact<{ input: UpsertRowLevelPermissionPredicatesInput; @@ -7413,7 +7413,7 @@ export type UpsertRowLevelPermissionPredicatesMutation = { __typename?: 'Mutatio export type GetRolesQueryVariables = Exact<{ [key: string]: never; }>; -export type GetRolesQuery = { __typename?: 'Query', getRoles: Array<{ __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, canAccessAllTools: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean, canBeAssignedToUsers: boolean, canBeAssignedToAgents: boolean, canBeAssignedToApiKeys: boolean, workspaceMembers: Array<{ __typename?: 'WorkspaceMember', id: string, avatarUrl?: string | null, userEmail: string, userWorkspaceId?: string | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }>, agents: Array<{ __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, modelConfiguration?: any | null, evaluationInputs: Array, applicationId?: string | null, createdAt: string, updatedAt: string }>, apiKeys: Array<{ __typename?: 'ApiKeyForRole', id: string, name: string, expiresAt: string, revokedAt?: string | null }>, permissionFlags?: Array<{ __typename?: 'RolePermissionFlag', id: string, flag: PermissionFlagType, roleId: string }> | null, objectPermissions?: Array<{ __typename?: 'ObjectPermission', objectMetadataId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null, restrictedFields?: any | null, rowLevelPermissionPredicates?: Array<{ __typename?: 'RowLevelPermissionPredicate', id: string, fieldMetadataId: string, objectMetadataId: string, operand: RowLevelPermissionPredicateOperand, subFieldName?: string | null, workspaceMemberFieldMetadataId?: string | null, workspaceMemberSubFieldName?: string | null, rowLevelPermissionPredicateGroupId?: string | null, positionInRowLevelPermissionPredicateGroup?: number | null, roleId: string, value?: any | null }> | null, rowLevelPermissionPredicateGroups?: Array<{ __typename?: 'RowLevelPermissionPredicateGroup', id: string, parentRowLevelPermissionPredicateGroupId?: string | null, logicalOperator: RowLevelPermissionPredicateGroupLogicalOperator, positionInRowLevelPermissionPredicateGroup?: number | null, roleId: string, objectMetadataId: string }> | null }> | null, fieldPermissions?: Array<{ __typename?: 'FieldPermission', objectMetadataId: string, fieldMetadataId: string, canReadFieldValue?: boolean | null, canUpdateFieldValue?: boolean | null, id: string, roleId: string }> | null, rowLevelPermissionPredicates?: Array<{ __typename?: 'RowLevelPermissionPredicate', id: string, fieldMetadataId: string, objectMetadataId: string, operand: RowLevelPermissionPredicateOperand, subFieldName?: string | null, workspaceMemberFieldMetadataId?: string | null, workspaceMemberSubFieldName?: string | null, rowLevelPermissionPredicateGroupId?: string | null, positionInRowLevelPermissionPredicateGroup?: number | null, roleId: string, value?: any | null }> | null, rowLevelPermissionPredicateGroups?: Array<{ __typename?: 'RowLevelPermissionPredicateGroup', id: string, parentRowLevelPermissionPredicateGroupId?: string | null, logicalOperator: RowLevelPermissionPredicateGroupLogicalOperator, positionInRowLevelPermissionPredicateGroup?: number | null, roleId: string, objectMetadataId: string }> | null }> }; +export type GetRolesQuery = { __typename?: 'Query', getRoles: Array<{ __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, canAccessAllTools: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean, canBeAssignedToUsers: boolean, canBeAssignedToAgents: boolean, canBeAssignedToApiKeys: boolean, workspaceMembers: Array<{ __typename?: 'WorkspaceMember', id: string, avatarUrl?: string | null, userEmail: string, userWorkspaceId?: string | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }>, agents: Array<{ __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, modelConfiguration?: any | null, evaluationInputs: Array, applicationId?: string | null, createdAt: string, updatedAt: string }>, apiKeys: Array<{ __typename?: 'ApiKeyForRole', id: string, name: string, expiresAt: string, revokedAt?: string | null }>, permissionFlags?: Array<{ __typename?: 'RolePermissionFlag', id: string, flag: string, roleId: string }> | null, objectPermissions?: Array<{ __typename?: 'ObjectPermission', objectMetadataId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null, restrictedFields?: any | null, rowLevelPermissionPredicates?: Array<{ __typename?: 'RowLevelPermissionPredicate', id: string, fieldMetadataId: string, objectMetadataId: string, operand: RowLevelPermissionPredicateOperand, subFieldName?: string | null, workspaceMemberFieldMetadataId?: string | null, workspaceMemberSubFieldName?: string | null, rowLevelPermissionPredicateGroupId?: string | null, positionInRowLevelPermissionPredicateGroup?: number | null, roleId: string, value?: any | null }> | null, rowLevelPermissionPredicateGroups?: Array<{ __typename?: 'RowLevelPermissionPredicateGroup', id: string, parentRowLevelPermissionPredicateGroupId?: string | null, logicalOperator: RowLevelPermissionPredicateGroupLogicalOperator, positionInRowLevelPermissionPredicateGroup?: number | null, roleId: string, objectMetadataId: string }> | null }> | null, fieldPermissions?: Array<{ __typename?: 'FieldPermission', objectMetadataId: string, fieldMetadataId: string, canReadFieldValue?: boolean | null, canUpdateFieldValue?: boolean | null, id: string, roleId: string }> | null, rowLevelPermissionPredicates?: Array<{ __typename?: 'RowLevelPermissionPredicate', id: string, fieldMetadataId: string, objectMetadataId: string, operand: RowLevelPermissionPredicateOperand, subFieldName?: string | null, workspaceMemberFieldMetadataId?: string | null, workspaceMemberSubFieldName?: string | null, rowLevelPermissionPredicateGroupId?: string | null, positionInRowLevelPermissionPredicateGroup?: number | null, roleId: string, value?: any | null }> | null, rowLevelPermissionPredicateGroups?: Array<{ __typename?: 'RowLevelPermissionPredicateGroup', id: string, parentRowLevelPermissionPredicateGroupId?: string | null, logicalOperator: RowLevelPermissionPredicateGroupLogicalOperator, positionInRowLevelPermissionPredicateGroup?: number | null, roleId: string, objectMetadataId: string }> | null }> }; export type CreateApprovedAccessDomainMutationVariables = Exact<{ input: CreateApprovedAccessDomainInput; diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/ai-agent-action/components/WorkflowAiAgentPermissionsFlagList.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/ai-agent-action/components/WorkflowAiAgentPermissionsFlagList.tsx index 1c4d29b46c..e21991c134 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/ai-agent-action/components/WorkflowAiAgentPermissionsFlagList.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/ai-agent-action/components/WorkflowAiAgentPermissionsFlagList.tsx @@ -1,16 +1,15 @@ import { SidePanelGroup } from '@/side-panel/components/SidePanelGroup'; import { type SettingsRolePermissionsSettingPermission } from '@/settings/roles/role-permissions/permission-flags/types/SettingsRolePermissionsSettingPermission'; -import { type PermissionFlagType } from '~/generated-metadata/graphql'; import { WorkflowAiAgentPermissionsFlagRow } from './WorkflowAiAgentPermissionsFlagRow'; type WorkflowAiAgentPermissionsFlagListProps = { title: string; permissions: SettingsRolePermissionsSettingPermission[]; - enabledPermissionFlagKeys: PermissionFlagType[]; + enabledPermissionFlagKeys: string[]; readonly: boolean; showDeleteButton?: boolean; - onAddPermissionFlag?: (permissionKey: PermissionFlagType) => void; - onDeletePermissionFlag?: (permissionKey: PermissionFlagType) => void; + onAddPermissionFlag?: (permissionKey: string) => void; + onDeletePermissionFlag?: (permissionKey: string) => void; }; export const WorkflowAiAgentPermissionsFlagList = ({ diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/ai-agent-action/components/workflowAiAgentPermissions.utils.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/ai-agent-action/components/workflowAiAgentPermissions.utils.ts index cdd6267d3a..6b9c628dc8 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/ai-agent-action/components/workflowAiAgentPermissions.utils.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/ai-agent-action/components/workflowAiAgentPermissions.utils.ts @@ -1,4 +1,3 @@ -import type { PermissionFlagType } from '~/generated-metadata/graphql'; import { filterBySearchQuery } from '~/utils/filterBySearchQuery'; export type FilteredPermissionsResult = { @@ -7,14 +6,14 @@ export type FilteredPermissionsResult = { }; export const getFilteredPermissions = < - T extends { key: PermissionFlagType; name: string }, + T extends { key: string; name: string }, >({ permissions, permissionFlagKeys, searchQuery, }: { permissions: T[]; - permissionFlagKeys: PermissionFlagType[]; + permissionFlagKeys: string[]; searchQuery: string; }): FilteredPermissionsResult => { const filteredPermissions = filterBySearchQuery({ diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/ai-agent-action/hooks/useWorkflowAiAgentPermissionActions.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/ai-agent-action/hooks/useWorkflowAiAgentPermissionActions.ts index 503054afa3..748496b288 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/ai-agent-action/hooks/useWorkflowAiAgentPermissionActions.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/ai-agent-action/hooks/useWorkflowAiAgentPermissionActions.ts @@ -16,7 +16,6 @@ import { useMutation } from '@apollo/client/react'; import { type Agent, type ObjectPermission, - type PermissionFlagType, AssignRoleToAgentDocument, CreateOneRoleDocument, UpsertObjectPermissionsDocument, @@ -26,7 +25,7 @@ import { type UseWorkflowAiAgentPermissionActionsParams = { readonly: boolean; objectPermissions: ObjectPermission[]; - permissionFlagKeys: PermissionFlagType[]; + permissionFlagKeys: string[]; refetchAgentAndRoles: () => Promise<{ refetchedAgent?: Agent }>; }; @@ -67,7 +66,7 @@ export const useWorkflowAiAgentPermissionActions = ({ const permissionFlagLabelMap = useMemo( () => [...settingsPermissionsConfig, ...actionPermissionsConfig].reduce< - Partial> + Partial> >((acc, permission) => { acc[permission.key] = permission.name; return acc; @@ -296,9 +295,7 @@ export const useWorkflowAiAgentPermissionActions = ({ } }; - const handleAddPermissionFlag = async ( - permissionFlagKey: PermissionFlagType, - ) => { + const handleAddPermissionFlag = async (permissionFlagKey: string) => { if (readonly) { return; } @@ -323,9 +320,7 @@ export const useWorkflowAiAgentPermissionActions = ({ setWorkflowAiAgentPermissionsSelectedObjectId(undefined); }; - const handleDeletePermissionFlag = async ( - permissionFlagKey: PermissionFlagType, - ) => { + const handleDeletePermissionFlag = async (permissionFlagKey: string) => { if (!isDefined(roleId) || readonly) { return; } diff --git a/packages/twenty-sdk/src/cli/__tests__/apps/minimal-app/__integration__/app-dev/expected-manifest.ts b/packages/twenty-sdk/src/cli/__tests__/apps/minimal-app/__integration__/app-dev/expected-manifest.ts index df578fb92f..ea7c517d32 100644 --- a/packages/twenty-sdk/src/cli/__tests__/apps/minimal-app/__integration__/app-dev/expected-manifest.ts +++ b/packages/twenty-sdk/src/cli/__tests__/apps/minimal-app/__integration__/app-dev/expected-manifest.ts @@ -16,6 +16,7 @@ export const EXPECTED_MANIFEST: Manifest = { packageJsonChecksum: '[checksum]', yarnLockChecksum: '[checksum]', }, + permissionFlags: [], skills: [], agents: [], publicAssets: [], diff --git a/packages/twenty-sdk/src/cli/__tests__/apps/rich-app/__integration__/app-dev/expected-manifest.ts b/packages/twenty-sdk/src/cli/__tests__/apps/rich-app/__integration__/app-dev/expected-manifest.ts index 5f876db2ce..bad203a5bf 100644 --- a/packages/twenty-sdk/src/cli/__tests__/apps/rich-app/__integration__/app-dev/expected-manifest.ts +++ b/packages/twenty-sdk/src/cli/__tests__/apps/rich-app/__integration__/app-dev/expected-manifest.ts @@ -13,6 +13,7 @@ import { export const EXPECTED_MANIFEST: Manifest = { commandMenuItems: [], + permissionFlags: [], pageLayouts: [], pageLayoutTabs: [ { diff --git a/packages/twenty-sdk/src/cli/utilities/build/common/plugins/__tests__/__snapshots__/stub-twenty-sdk-define.plugin.spec.ts.snap b/packages/twenty-sdk/src/cli/utilities/build/common/plugins/__tests__/__snapshots__/stub-twenty-sdk-define.plugin.spec.ts.snap index 2a8c59cba3..7c659fd58b 100644 --- a/packages/twenty-sdk/src/cli/utilities/build/common/plugins/__tests__/__snapshots__/stub-twenty-sdk-define.plugin.spec.ts.snap +++ b/packages/twenty-sdk/src/cli/utilities/build/common/plugins/__tests__/__snapshots__/stub-twenty-sdk-define.plugin.spec.ts.snap @@ -43,6 +43,7 @@ exports[`stub-twenty-sdk-define plugin > matches the recorded export partition 1 "defineObject", "definePageLayout", "definePageLayoutTab", + "definePermissionFlag", "definePostInstallLogicFunction", "definePreInstallLogicFunction", "defineRole", diff --git a/packages/twenty-sdk/src/cli/utilities/build/manifest/__tests__/manifest-validate.spec.ts b/packages/twenty-sdk/src/cli/utilities/build/manifest/__tests__/manifest-validate.spec.ts index 210951afef..695e3f6682 100644 --- a/packages/twenty-sdk/src/cli/utilities/build/manifest/__tests__/manifest-validate.spec.ts +++ b/packages/twenty-sdk/src/cli/utilities/build/manifest/__tests__/manifest-validate.spec.ts @@ -31,6 +31,7 @@ const validManifest: Manifest = { frontComponents: [], fields: [], logicFunctions: [], + permissionFlags: [], roles: [], skills: [], agents: [], diff --git a/packages/twenty-sdk/src/cli/utilities/build/manifest/manifest-build.ts b/packages/twenty-sdk/src/cli/utilities/build/manifest/manifest-build.ts index 1b48558de8..eea4d8e234 100644 --- a/packages/twenty-sdk/src/cli/utilities/build/manifest/manifest-build.ts +++ b/packages/twenty-sdk/src/cli/utilities/build/manifest/manifest-build.ts @@ -40,6 +40,7 @@ import { type ObjectManifest, type PageLayoutManifest, type PageLayoutTabManifest, + type PermissionFlagManifest, type PostInstallLogicFunctionApplicationManifest, type PreInstallLogicFunctionApplicationManifest, type RoleManifest, @@ -85,6 +86,7 @@ export const buildManifest = async ( const objects: ObjectManifest[] = []; const fields: FieldManifest[] = []; const indexes: IndexManifest[] = []; + const permissionFlags: PermissionFlagManifest[] = []; const roles: RoleManifest[] = []; const skills: SkillManifest[] = []; const agents: AgentManifest[] = []; @@ -106,6 +108,7 @@ export const buildManifest = async ( const objectsFilePaths: string[] = []; const fieldsFilePaths: string[] = []; const indexesFilePaths: string[] = []; + const permissionFlagsFilePaths: string[] = []; const rolesFilePaths: string[] = []; const skillsFilePaths: string[] = []; const agentsFilePaths: string[] = []; @@ -193,6 +196,17 @@ export const buildManifest = async ( fieldsFilePaths.push(relativePath); break; } + case ManifestEntityKey.PermissionFlags: { + const extract = await extractManifestFromFile({ + appPath, + filePath, + }); + permissionFlags.push(extract.config); + errors.push(...extract.errors); + warnings.push(...(extract.warnings ?? [])); + permissionFlagsFilePaths.push(relativePath); + break; + } case ManifestEntityKey.Roles: { const extract = await extractManifestFromFile({ appPath, @@ -547,6 +561,7 @@ export const buildManifest = async ( objects: objects.sort(byId), fields: fields.sort(byId), indexes: indexes.sort(byId), + permissionFlags: permissionFlags.sort(byId), roles: roles.sort(byId), skills: skills.sort(byId), agents: agents.sort(byId), @@ -566,6 +581,7 @@ export const buildManifest = async ( objects: objectsFilePaths, fields: fieldsFilePaths, indexes: indexesFilePaths, + permissionFlags: permissionFlagsFilePaths, roles: rolesFilePaths, skills: skillsFilePaths, agents: agentsFilePaths, diff --git a/packages/twenty-sdk/src/cli/utilities/build/manifest/manifest-extract-config.ts b/packages/twenty-sdk/src/cli/utilities/build/manifest/manifest-extract-config.ts index aedf60c257..70b8cdbb44 100644 --- a/packages/twenty-sdk/src/cli/utilities/build/manifest/manifest-extract-config.ts +++ b/packages/twenty-sdk/src/cli/utilities/build/manifest/manifest-extract-config.ts @@ -9,6 +9,7 @@ export enum TargetFunction { DefinePostInstallLogicFunction = 'definePostInstallLogicFunction', DefinePreInstallLogicFunction = 'definePreInstallLogicFunction', DefineObject = 'defineObject', + DefinePermissionFlag = 'definePermissionFlag', DefineRole = 'defineRole', DefineSkill = 'defineSkill', DefineAgent = 'defineAgent', @@ -27,6 +28,7 @@ export enum ManifestEntityKey { Indexes = 'indexes', LogicFunctions = 'logicFunctions', Objects = 'objects', + PermissionFlags = 'permissionFlags', Roles = 'roles', Skills = 'skills', Agents = 'agents', @@ -56,6 +58,7 @@ export const TARGET_FUNCTION_TO_ENTITY_KEY_MAPPING: Record< [TargetFunction.DefinePreInstallLogicFunction]: ManifestEntityKey.LogicFunctions, [TargetFunction.DefineObject]: ManifestEntityKey.Objects, + [TargetFunction.DefinePermissionFlag]: ManifestEntityKey.PermissionFlags, [TargetFunction.DefineRole]: ManifestEntityKey.Roles, [TargetFunction.DefineSkill]: ManifestEntityKey.Skills, [TargetFunction.DefineAgent]: ManifestEntityKey.Agents, diff --git a/packages/twenty-sdk/src/sdk/define/common/types/define-entity.type.ts b/packages/twenty-sdk/src/sdk/define/common/types/define-entity.type.ts index 5fafa633d0..1ba6fc6396 100644 --- a/packages/twenty-sdk/src/sdk/define/common/types/define-entity.type.ts +++ b/packages/twenty-sdk/src/sdk/define/common/types/define-entity.type.ts @@ -6,6 +6,7 @@ import { type LogicFunctionConfig } from '@/sdk/define/logic-functions/logic-fun import { type ObjectConfig } from '@/sdk/define/objects/object-config'; import { type PageLayoutConfig } from '@/sdk/define/page-layouts/page-layout-config'; import { type PageLayoutTabConfig } from '@/sdk/define/page-layouts/page-layout-tab-config'; +import { type PermissionFlagConfig } from '@/sdk/define/permission-flags/permission-flag-config'; import { type ViewConfig } from '@/sdk/define/views/view-config'; import { type PostInstallLogicFunctionConfig } from '@/sdk/define/logic-functions/post-install-logic-function-config'; import { type PreInstallLogicFunctionConfig } from '@/sdk/define/logic-functions/pre-install-logic-function-config'; @@ -42,6 +43,7 @@ export type DefinableEntity = | NavigationMenuItemManifest | PageLayoutConfig | PageLayoutTabConfig + | PermissionFlagConfig | CommandMenuItemConfig; export type DefineEntity = ( diff --git a/packages/twenty-sdk/src/sdk/define/index.ts b/packages/twenty-sdk/src/sdk/define/index.ts index 5263eae91a..e62eb06709 100644 --- a/packages/twenty-sdk/src/sdk/define/index.ts +++ b/packages/twenty-sdk/src/sdk/define/index.ts @@ -91,6 +91,13 @@ export type { PageLayoutWidgetManifest, } from 'twenty-shared/application'; +export { definePermissionFlag } from '@/sdk/define/permission-flags/define-permission-flag'; +export type { PermissionFlagConfig } from '@/sdk/define/permission-flags/permission-flag-config'; +export type { + PermissionFlagManifest, + PermissionFlagPermissionType, +} from 'twenty-shared/application'; + export { defineApplicationRole } from '@/sdk/define/roles/define-application-role'; export { defineRole } from '@/sdk/define/roles/define-role'; export { SystemPermissionFlag } from 'twenty-shared/constants'; diff --git a/packages/twenty-sdk/src/sdk/define/permission-flags/define-permission-flag.ts b/packages/twenty-sdk/src/sdk/define/permission-flags/define-permission-flag.ts new file mode 100644 index 0000000000..8bcb158d50 --- /dev/null +++ b/packages/twenty-sdk/src/sdk/define/permission-flags/define-permission-flag.ts @@ -0,0 +1,23 @@ +import { type DefineEntity } from '@/sdk/define/common/types/define-entity.type'; +import { createValidationResult } from '@/sdk/define/common/utils/create-validation-result'; +import { type PermissionFlagConfig } from '@/sdk/define/permission-flags/permission-flag-config'; + +export const definePermissionFlag: DefineEntity = ( + config, +) => { + const errors: string[] = []; + + if (!config.universalIdentifier) { + errors.push('Permission flag must have a universalIdentifier'); + } + + if (!config.key) { + errors.push('Permission flag must have a key'); + } + + if (!config.label) { + errors.push('Permission flag must have a label'); + } + + return createValidationResult({ config, errors }); +}; diff --git a/packages/twenty-sdk/src/sdk/define/permission-flags/permission-flag-config.ts b/packages/twenty-sdk/src/sdk/define/permission-flags/permission-flag-config.ts new file mode 100644 index 0000000000..7922f67818 --- /dev/null +++ b/packages/twenty-sdk/src/sdk/define/permission-flags/permission-flag-config.ts @@ -0,0 +1,3 @@ +import { type PermissionFlagManifest } from 'twenty-shared/application'; + +export type PermissionFlagConfig = PermissionFlagManifest; diff --git a/packages/twenty-server/src/engine/core-modules/application/application-manifest/application-manifest-migration.service.ts b/packages/twenty-server/src/engine/core-modules/application/application-manifest/application-manifest-migration.service.ts index a8fc976167..007d228354 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application-manifest/application-manifest-migration.service.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application-manifest/application-manifest-migration.service.ts @@ -71,6 +71,7 @@ export class ApplicationManifestMigrationService { fields: [], logicFunctions: [preInstallLogicFunctionManifest], frontComponents: [], + permissionFlags: [], roles: [], skills: [], agents: [], diff --git a/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-permission-flag-manifest-to-universal-flat-permission-flag.util.ts b/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-permission-flag-manifest-to-universal-flat-permission-flag.util.ts new file mode 100644 index 0000000000..e389fb1902 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-permission-flag-manifest-to-universal-flat-permission-flag.util.ts @@ -0,0 +1,26 @@ +import { type PermissionFlagManifest } from 'twenty-shared/application'; + +import { type UniversalFlatPermissionFlag } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-permission-flag.type'; + +export const fromPermissionFlagManifestToUniversalFlatPermissionFlag = ({ + permissionFlagManifest, + applicationUniversalIdentifier, + now, +}: { + permissionFlagManifest: PermissionFlagManifest; + applicationUniversalIdentifier: string; + now: string; +}): UniversalFlatPermissionFlag => { + return { + universalIdentifier: permissionFlagManifest.universalIdentifier, + applicationUniversalIdentifier, + key: permissionFlagManifest.key, + label: permissionFlagManifest.label, + description: permissionFlagManifest.description ?? null, + icon: permissionFlagManifest.icon ?? null, + permissionType: 'tool', + rolePermissionFlagUniversalIdentifiers: [], + createdAt: now, + updatedAt: now, + }; +}; diff --git a/packages/twenty-server/src/engine/core-modules/application/application-manifest/utils/compute-application-manifest-all-universal-flat-entity-maps.util.ts b/packages/twenty-server/src/engine/core-modules/application/application-manifest/utils/compute-application-manifest-all-universal-flat-entity-maps.util.ts index 47f3966d8a..33cb027b3a 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application-manifest/utils/compute-application-manifest-all-universal-flat-entity-maps.util.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application-manifest/utils/compute-application-manifest-all-universal-flat-entity-maps.util.ts @@ -19,6 +19,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 { fromPermissionFlagManifestToUniversalFlatPermissionFlag } from 'src/engine/core-modules/application/application-manifest/converters/from-permission-flag-manifest-to-universal-flat-permission-flag.util'; import { fromPermissionFlagToUniversalFlatRolePermissionFlag } from 'src/engine/core-modules/application/application-manifest/converters/from-permission-flag-to-universal-flat-role-permission-flag.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'; @@ -246,6 +247,19 @@ export const computeApplicationManifestAllUniversalFlatEntityMaps = ({ }); } + for (const permissionFlagManifest of manifest.permissionFlags ?? []) { + addUniversalFlatEntityToUniversalFlatEntityMapsThroughMutationOrThrow({ + universalFlatEntity: + fromPermissionFlagManifestToUniversalFlatPermissionFlag({ + permissionFlagManifest, + applicationUniversalIdentifier, + now, + }), + universalFlatEntityMapsToMutate: + allUniversalFlatEntityMaps.flatPermissionFlagMaps, + }); + } + for (const roleManifest of manifest.roles) { addUniversalFlatEntityToUniversalFlatEntityMapsThroughMutationOrThrow({ universalFlatEntity: fromRoleManifestToUniversalFlatRole({ diff --git a/packages/twenty-server/src/engine/core-modules/application/application-marketplace/utils/__tests__/resolve-manifest-asset-urls.util.spec.ts b/packages/twenty-server/src/engine/core-modules/application/application-marketplace/utils/__tests__/resolve-manifest-asset-urls.util.spec.ts index c6fecbeafd..cb876e9178 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application-marketplace/utils/__tests__/resolve-manifest-asset-urls.util.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application-marketplace/utils/__tests__/resolve-manifest-asset-urls.util.spec.ts @@ -18,6 +18,7 @@ const buildMinimalManifest = ( fields: [], logicFunctions: [], frontComponents: [], + permissionFlags: [], roles: [], skills: [], agents: [], diff --git a/packages/twenty-server/src/engine/metadata-modules/role-permission-flag/dtos/role-permission-flag.dto.ts b/packages/twenty-server/src/engine/metadata-modules/role-permission-flag/dtos/role-permission-flag.dto.ts index 5ce8ff3d60..c9f8d6f02a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/role-permission-flag/dtos/role-permission-flag.dto.ts +++ b/packages/twenty-server/src/engine/metadata-modules/role-permission-flag/dtos/role-permission-flag.dto.ts @@ -1,7 +1,5 @@ import { Field, ObjectType } from '@nestjs/graphql'; -import { PermissionFlagType } from 'twenty-shared/constants'; - import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; @ObjectType('RolePermissionFlag') @@ -12,6 +10,6 @@ export class RolePermissionFlagDTO { @Field(() => UUIDScalarType, { nullable: false }) roleId: string; - @Field({ nullable: false }) - flag: PermissionFlagType; + @Field(() => String, { nullable: false }) + flag: string; } diff --git a/packages/twenty-server/src/engine/metadata-modules/role-permission-flag/dtos/upsert-permission-flags.input.ts b/packages/twenty-server/src/engine/metadata-modules/role-permission-flag/dtos/upsert-permission-flags.input.ts index 063944d3e2..2cb2a6a866 100644 --- a/packages/twenty-server/src/engine/metadata-modules/role-permission-flag/dtos/upsert-permission-flags.input.ts +++ b/packages/twenty-server/src/engine/metadata-modules/role-permission-flag/dtos/upsert-permission-flags.input.ts @@ -1,7 +1,6 @@ import { Field, InputType } from '@nestjs/graphql'; -import { IsArray, IsEnum, IsNotEmpty, IsUUID } from 'class-validator'; -import { PermissionFlagType } from 'twenty-shared/constants'; +import { IsArray, IsNotEmpty, IsString, IsUUID } from 'class-validator'; import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; @@ -13,7 +12,7 @@ export class UpsertPermissionFlagsInput { roleId: string; @IsArray() - @IsEnum(PermissionFlagType, { each: true }) - @Field(() => [PermissionFlagType]) - permissionFlagKeys: PermissionFlagType[]; + @IsString({ each: true }) + @Field(() => [String]) + permissionFlagKeys: string[]; } diff --git a/packages/twenty-server/src/engine/metadata-modules/role-permission-flag/utils/from-flat-role-permission-flag-to-role-permission-flag-dto.util.ts b/packages/twenty-server/src/engine/metadata-modules/role-permission-flag/utils/from-flat-role-permission-flag-to-role-permission-flag-dto.util.ts index 63dee33461..69a6b5063c 100644 --- a/packages/twenty-server/src/engine/metadata-modules/role-permission-flag/utils/from-flat-role-permission-flag-to-role-permission-flag-dto.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/role-permission-flag/utils/from-flat-role-permission-flag-to-role-permission-flag-dto.util.ts @@ -1,4 +1,3 @@ -import { PermissionFlagType } from 'twenty-shared/constants'; import { isDefined } from 'twenty-shared/utils'; import { type FlatPermissionFlagMaps } from 'src/engine/metadata-modules/flat-permission-flag/types/flat-permission-flag-maps.type'; @@ -28,6 +27,6 @@ export const fromFlatRolePermissionFlagToRolePermissionFlagDto = ( return { id: flatRolePermissionFlag.id, roleId: flatRolePermissionFlag.roleId, - flag: permissionFlag.key as PermissionFlagType, + flag: permissionFlag.key, }; }; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-permission-flag-validator.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-permission-flag-validator.service.ts index 3ae08cf083..26c43fc8a9 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-permission-flag-validator.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-permission-flag-validator.service.ts @@ -55,9 +55,9 @@ export class FlatPermissionFlagValidatorService { }); } - const duplicateKey = Object.values( + const collidingPermissionFlag = Object.values( optimisticFlatPermissionFlagMaps.byUniversalIdentifier, - ).filter( + ).find( (definition) => isDefined(definition) && definition.key === flatPermissionFlagToValidate.key && @@ -65,11 +65,11 @@ export class FlatPermissionFlagValidatorService { flatPermissionFlagToValidate.universalIdentifier, ); - if (duplicateKey.length > 0) { + if (isDefined(collidingPermissionFlag)) { validationResult.errors.push({ code: PermissionFlagExceptionCode.PERMISSION_FLAG_ALREADY_EXISTS, - message: t`Permission flag definition with key ${flatPermissionFlagToValidate.key} already exists in this workspace`, - userFriendlyMessage: msg`A permission flag with this key already exists`, + message: t`Permission flag definition with key "${flatPermissionFlagToValidate.key}" is already registered in this workspace.`, + userFriendlyMessage: msg`Another application in this workspace has already registered a permission flag with this key.`, }); } diff --git a/packages/twenty-server/test/integration/graphql/suites/settings-permissions/granular-settings-permissions.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/settings-permissions/granular-settings-permissions.integration-spec.ts index 50f365d008..e64f6e766b 100644 --- a/packages/twenty-server/test/integration/graphql/suites/settings-permissions/granular-settings-permissions.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/settings-permissions/granular-settings-permissions.integration-spec.ts @@ -62,7 +62,7 @@ describe('Granular settings permissions', () => { mutation UpsertPermissionFlags { upsertPermissionFlags(upsertPermissionFlagsInput: { roleId: "${customRoleId}" - permissionFlagKeys: [${PermissionFlagType.DATA_MODEL}, ${PermissionFlagType.WORKSPACE}, ${PermissionFlagType.WORKFLOWS}] + permissionFlagKeys: ["${PermissionFlagType.DATA_MODEL}", "${PermissionFlagType.WORKSPACE}", "${PermissionFlagType.WORKFLOWS}"] }) { id flag @@ -368,7 +368,7 @@ describe('Granular settings permissions', () => { mutation UpsertPermissionFlags { upsertPermissionFlags(upsertPermissionFlagsInput: { roleId: "${customRoleId}" - permissionFlagKeys: [${PermissionFlagType.DATA_MODEL}, ${PermissionFlagType.WORKSPACE}, ${PermissionFlagType.SECURITY}] + permissionFlagKeys: ["${PermissionFlagType.DATA_MODEL}", "${PermissionFlagType.WORKSPACE}", "${PermissionFlagType.SECURITY}"] }) { id flag @@ -420,7 +420,7 @@ describe('Granular settings permissions', () => { mutation UpsertPermissionFlags { upsertPermissionFlags(upsertPermissionFlagsInput: { roleId: "${customRoleId}" - permissionFlagKeys: [${PermissionFlagType.DATA_MODEL}, ${PermissionFlagType.WORKSPACE}] + permissionFlagKeys: ["${PermissionFlagType.DATA_MODEL}", "${PermissionFlagType.WORKSPACE}"] }) { id flag diff --git a/packages/twenty-server/test/integration/graphql/suites/settings-permissions/roles.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/settings-permissions/roles.integration-spec.ts index e15678fc70..2b4aaad6fc 100644 --- a/packages/twenty-server/test/integration/graphql/suites/settings-permissions/roles.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/settings-permissions/roles.integration-spec.ts @@ -565,7 +565,7 @@ describe('roles permissions', () => { roleId: string; }) => ` mutation UpsertPermissionFlags { - upsertPermissionFlags(upsertPermissionFlagsInput: {roleId: "${roleId}", permissionFlagKeys: [${PermissionFlagType.DATA_MODEL}]}) { + upsertPermissionFlags(upsertPermissionFlagsInput: {roleId: "${roleId}", permissionFlagKeys: ["${PermissionFlagType.DATA_MODEL}"]}) { id roleId flag diff --git a/packages/twenty-server/test/integration/metadata/suites/application/utils/build-base-manifest.util.ts b/packages/twenty-server/test/integration/metadata/suites/application/utils/build-base-manifest.util.ts index d5e03898d9..1f6acc869f 100644 --- a/packages/twenty-server/test/integration/metadata/suites/application/utils/build-base-manifest.util.ts +++ b/packages/twenty-server/test/integration/metadata/suites/application/utils/build-base-manifest.util.ts @@ -25,6 +25,7 @@ export const buildBaseManifest = ({ description: 'A test role', }, ], + permissionFlags: [], skills: [], agents: [], objects: [], diff --git a/packages/twenty-server/test/integration/metadata/suites/role-permission-flag/__snapshots__/failing-permission-flag-upsert.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/role-permission-flag/__snapshots__/failing-permission-flag-upsert.integration-spec.ts.snap index 41961024b0..1b6709b97f 100644 --- a/packages/twenty-server/test/integration/metadata/suites/role-permission-flag/__snapshots__/failing-permission-flag-upsert.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/role-permission-flag/__snapshots__/failing-permission-flag-upsert.integration-spec.ts.snap @@ -4,13 +4,11 @@ exports[`Permission flag upsert should fail when permissionFlagKeys contains inv { "extensions": { "code": "BAD_USER_INPUT", - "http": { - "status": 400, - }, - "userFriendlyMessage": "An error occurred.", + "subCode": "INVALID_SETTING_PERMISSIONS", + "userFriendlyMessage": "Some of the permissions you selected are not valid. Please try again with valid permission settings.", }, - "message": "Value "INVALID_FLAG" does not exist in "PermissionFlagType" enum.", - "name": "GraphQLError", + "message": "Invalid permission setting (unknown value): INVALID_FLAG", + "name": "UserInputError", } `; diff --git a/packages/twenty-shared/src/application/index.ts b/packages/twenty-shared/src/application/index.ts index 5ba7c3b733..47a7081f96 100644 --- a/packages/twenty-shared/src/application/index.ts +++ b/packages/twenty-shared/src/application/index.ts @@ -55,6 +55,10 @@ export type { PageLayoutTabManifest, PageLayoutManifest, } from './pageLayoutManifestType'; +export type { + PermissionFlagPermissionType, + PermissionFlagManifest, +} from './permissionFlagManifestType'; export type { PostInstallLogicFunctionApplicationManifest } from './postInstallLogicFunctionApplicationType'; export type { PreInstallLogicFunctionApplicationManifest } from './preInstallLogicFunctionApplicationType'; export type { diff --git a/packages/twenty-shared/src/application/manifestType.ts b/packages/twenty-shared/src/application/manifestType.ts index d5e17e6e29..8c7bea1cfd 100644 --- a/packages/twenty-shared/src/application/manifestType.ts +++ b/packages/twenty-shared/src/application/manifestType.ts @@ -15,6 +15,7 @@ import { type PageLayoutManifest, type PageLayoutTabManifest, } from './pageLayoutManifestType'; +import { type PermissionFlagManifest } from './permissionFlagManifestType'; import { type RoleManifest } from './roleManifestType'; import { type SkillManifest } from './skillManifestType'; import { type ViewManifest } from './viewManifestType'; @@ -26,6 +27,7 @@ export type Manifest = { indexes?: IndexManifest[]; logicFunctions: LogicFunctionManifest[]; frontComponents: FrontComponentManifest[]; + permissionFlags: PermissionFlagManifest[]; roles: RoleManifest[]; skills: SkillManifest[]; agents: AgentManifest[]; diff --git a/packages/twenty-shared/src/application/permissionFlagManifestType.ts b/packages/twenty-shared/src/application/permissionFlagManifestType.ts new file mode 100644 index 0000000000..8651e94a0e --- /dev/null +++ b/packages/twenty-shared/src/application/permissionFlagManifestType.ts @@ -0,0 +1,10 @@ +import { type SyncableEntityOptions } from '@/application/syncableEntityOptionsType'; + +export type PermissionFlagPermissionType = 'settings' | 'tool'; + +export type PermissionFlagManifest = SyncableEntityOptions & { + key: string; + label: string; + description?: string | null; + icon?: string | null; +};