diff --git a/packages/twenty-front/codegen-metadata.cjs b/packages/twenty-front/codegen-metadata.cjs index cb9ce7874a..d2bee36cb4 100644 --- a/packages/twenty-front/codegen-metadata.cjs +++ b/packages/twenty-front/codegen-metadata.cjs @@ -9,6 +9,7 @@ module.exports = { './src/modules/users/graphql/**/*.{ts,tsx}', './src/modules/views/graphql/**/*.{ts,tsx}', './src/modules/ai/graphql/**/*.{ts,tsx}', + './src/modules/applications/graphql/**/*.{ts,tsx}', './src/modules/workspace/graphql/**/*.{ts,tsx}', './src/modules/workspace-member/graphql/**/*.{ts,tsx}', diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index 52b16766f8..ce0ff121b4 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -51,6 +51,7 @@ export type AdminPanelWorkerQueueHealth = { export type Agent = { __typename?: 'Agent'; + applicationId?: Maybe; createdAt: Scalars['DateTime']; description?: Maybe; icon?: Maybe; @@ -200,6 +201,16 @@ export type AppTokenEdge = { node: AppToken; }; +export type Application = { + __typename?: 'Application'; + agents: Array; + description: Scalars['String']; + id: Scalars['UUID']; + name: Scalars['String']; + objects: Array; + serverlessFunctions: Array; +}; + export type ApprovedAccessDomain = { __typename?: 'ApprovedAccessDomain'; createdAt: Scalars['DateTime']; @@ -972,7 +983,6 @@ export type CronTrigger = { __typename?: 'CronTrigger'; createdAt: Scalars['DateTime']; id: Scalars['UUID']; - serverlessFunctionId: Scalars['String']; settings: Scalars['JSON']; updatedAt: Scalars['DateTime']; }; @@ -1006,7 +1016,6 @@ export type DatabaseEventTrigger = { __typename?: 'DatabaseEventTrigger'; createdAt: Scalars['DateTime']; id: Scalars['UUID']; - serverlessFunctionId: Scalars['String']; settings: Scalars['JSON']; updatedAt: Scalars['DateTime']; }; @@ -1226,6 +1235,7 @@ export type FeatureFlagDto = { export enum FeatureFlagKey { IS_AIRTABLE_INTEGRATION_ENABLED = 'IS_AIRTABLE_INTEGRATION_ENABLED', IS_AI_ENABLED = 'IS_AI_ENABLED', + IS_APPLICATION_ENABLED = 'IS_APPLICATION_ENABLED', IS_CALENDAR_VIEW_ENABLED = 'IS_CALENDAR_VIEW_ENABLED', IS_COMMON_API_ENABLED = 'IS_COMMON_API_ENABLED', IS_CORE_VIEW_ENABLED = 'IS_CORE_VIEW_ENABLED', @@ -2757,6 +2767,7 @@ export type NumberChartConfiguration = { export type Object = { __typename?: 'Object'; + applicationId?: Maybe; createdAt: Scalars['DateTime']; description?: Maybe; duplicateCriteria?: Maybe>>; @@ -3071,6 +3082,7 @@ export type Query = { findAgentHandoffs: Array; findDistantTablesWithStatus: Array; findManyAgents: Array; + findManyApplications: Array; findManyCronTriggers: Array; findManyDatabaseEventTriggers: Array; findManyPublicDomains: Array; @@ -3078,6 +3090,7 @@ export type Query = { findManyRouteTriggers: Array; findManyServerlessFunctions: Array; findOneAgent: Agent; + findOneApplication: Application; findOneCronTrigger: CronTrigger; findOneDatabaseEventTrigger: DatabaseEventTrigger; findOneRemoteServerById: RemoteServer; @@ -3211,6 +3224,11 @@ export type QueryFindOneAgentArgs = { }; +export type QueryFindOneApplicationArgs = { + id: Scalars['UUID']; +}; + + export type QueryFindOneCronTriggerArgs = { input: CronTriggerIdInput; }; @@ -3596,9 +3614,7 @@ export type RouteTrigger = { id: Scalars['ID']; isAuthRequired: Scalars['Boolean']; path: Scalars['String']; - serverlessFunctionId: Scalars['String']; updatedAt: Scalars['DateTime']; - workspaceId: Scalars['String']; }; export type RouteTriggerIdInput = { @@ -3684,11 +3700,14 @@ export type Sentry = { export type ServerlessFunction = { __typename?: 'ServerlessFunction'; createdAt: Scalars['DateTime']; + cronTriggers?: Maybe>; + databaseEventTriggers?: Maybe>; description?: Maybe; id: Scalars['UUID']; latestVersion?: Maybe; name: Scalars['String']; publishedVersions: Array; + routeTriggers?: Maybe>; runtime: Scalars['String']; timeoutSeconds: Scalars['Float']; updatedAt: Scalars['DateTime']; @@ -4648,7 +4667,7 @@ export type WorkspaceUrlsAndId = { workspaceUrls: WorkspaceUrls; }; -export type AgentFieldsFragment = { __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, createdAt: string, updatedAt: string }; +export type AgentFieldsFragment = { __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, applicationId?: string | null, createdAt: string, updatedAt: string }; export type AssignRoleToAgentMutationVariables = Exact<{ agentId: Scalars['UUID']; @@ -4677,14 +4696,14 @@ export type CreateOneAgentMutationVariables = Exact<{ }>; -export type CreateOneAgentMutation = { __typename?: 'Mutation', createOneAgent: { __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, createdAt: string, updatedAt: string } }; +export type CreateOneAgentMutation = { __typename?: 'Mutation', createOneAgent: { __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, applicationId?: string | null, createdAt: string, updatedAt: string } }; export type DeleteOneAgentMutationVariables = Exact<{ input: AgentIdInput; }>; -export type DeleteOneAgentMutation = { __typename?: 'Mutation', deleteOneAgent: { __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, createdAt: string, updatedAt: string } }; +export type DeleteOneAgentMutation = { __typename?: 'Mutation', deleteOneAgent: { __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, applicationId?: string | null, createdAt: string, updatedAt: string } }; export type RemoveAgentHandoffMutationVariables = Exact<{ input: RemoveAgentHandoffInput; @@ -4705,7 +4724,7 @@ export type UpdateOneAgentMutationVariables = Exact<{ }>; -export type UpdateOneAgentMutation = { __typename?: 'Mutation', updateOneAgent: { __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, createdAt: string, updatedAt: string } }; +export type UpdateOneAgentMutation = { __typename?: 'Mutation', updateOneAgent: { __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, applicationId?: string | null, createdAt: string, updatedAt: string } }; export type FindAgentHandoffTargetsQueryVariables = Exact<{ input: AgentIdInput; @@ -4724,14 +4743,14 @@ export type FindAgentHandoffsQuery = { __typename?: 'Query', findAgentHandoffs: export type FindManyAgentsQueryVariables = Exact<{ [key: string]: never; }>; -export type FindManyAgentsQuery = { __typename?: 'Query', findManyAgents: 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, createdAt: string, updatedAt: string }> }; +export type FindManyAgentsQuery = { __typename?: 'Query', findManyAgents: 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, applicationId?: string | null, createdAt: string, updatedAt: string }> }; export type FindOneAgentQueryVariables = Exact<{ id: Scalars['UUID']; }>; -export type FindOneAgentQuery = { __typename?: 'Query', findOneAgent: { __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, createdAt: string, updatedAt: string } }; +export type FindOneAgentQuery = { __typename?: 'Query', findOneAgent: { __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, applicationId?: string | null, createdAt: string, updatedAt: string } }; export type GetAgentChatMessagesQueryVariables = Exact<{ threadId: Scalars['UUID']; @@ -4757,6 +4776,20 @@ export type TrackAnalyticsMutationVariables = Exact<{ export type TrackAnalyticsMutation = { __typename?: 'Mutation', trackAnalytics: { __typename?: 'Analytics', success: boolean } }; +export type ApplicationFieldsFragment = { __typename?: 'Application', id: string, name: string, description: 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, applicationId?: string | null, createdAt: string, updatedAt: string }>, objects: Array<{ __typename?: 'Object', id: string, nameSingular: string, namePlural: string, labelSingular: string, labelPlural: string, description?: string | null, icon?: string | null, isCustom: boolean, isRemote: boolean, isActive: boolean, isSystem: boolean, isUIReadOnly: boolean, createdAt: string, updatedAt: string, labelIdentifierFieldMetadataId?: string | null, imageIdentifierFieldMetadataId?: string | null, applicationId?: string | null, shortcut?: string | null, isLabelSyncedWithName: boolean, isSearchable: boolean, duplicateCriteria?: Array> | null, indexMetadataList: Array<{ __typename?: 'Index', id: string, createdAt: string, updatedAt: string, name: string, indexWhereClause?: string | null, indexType: IndexType, isUnique: boolean, isCustom?: boolean | null, indexFieldMetadataList: Array<{ __typename?: 'IndexField', id: string, fieldMetadataId: string, createdAt: string, updatedAt: string, order: number }> }>, fieldsList: Array<{ __typename?: 'Field', id: string, type: FieldMetadataType, name: string, label: string, description?: string | null, icon?: string | null, isCustom?: boolean | null, isActive?: boolean | null, isSystem?: boolean | null, isUIReadOnly?: boolean | null, isNullable?: boolean | null, isUnique?: boolean | null, createdAt: string, updatedAt: string, defaultValue?: any | null, options?: any | null, settings?: any | null, isLabelSyncedWithName?: boolean | null, relation?: { __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } } | null, morphRelations?: Array<{ __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } }> | null }> }>, serverlessFunctions: Array<{ __typename?: 'ServerlessFunction', id: string, name: string, description?: string | null, runtime: string, timeoutSeconds: number, latestVersion?: string | null, publishedVersions: Array, createdAt: string, updatedAt: string, cronTriggers?: Array<{ __typename?: 'CronTrigger', id: string, settings: any, createdAt: string, updatedAt: string }> | null, databaseEventTriggers?: Array<{ __typename?: 'DatabaseEventTrigger', id: string, settings: any, createdAt: string, updatedAt: string }> | null, routeTriggers?: Array<{ __typename?: 'RouteTrigger', id: string, path: string, isAuthRequired: boolean, httpMethod: HttpMethod, createdAt: string, updatedAt: string }> | null }> }; + +export type FindManyApplicationsQueryVariables = Exact<{ [key: string]: never; }>; + + +export type FindManyApplicationsQuery = { __typename?: 'Query', findManyApplications: Array<{ __typename?: 'Application', id: string, name: string, description: string }> }; + +export type FindOneApplicationQueryVariables = Exact<{ + id: Scalars['UUID']; +}>; + + +export type FindOneApplicationQuery = { __typename?: 'Query', findOneApplication: { __typename?: 'Application', id: string, name: string, description: 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, applicationId?: string | null, createdAt: string, updatedAt: string }>, objects: Array<{ __typename?: 'Object', id: string, nameSingular: string, namePlural: string, labelSingular: string, labelPlural: string, description?: string | null, icon?: string | null, isCustom: boolean, isRemote: boolean, isActive: boolean, isSystem: boolean, isUIReadOnly: boolean, createdAt: string, updatedAt: string, labelIdentifierFieldMetadataId?: string | null, imageIdentifierFieldMetadataId?: string | null, applicationId?: string | null, shortcut?: string | null, isLabelSyncedWithName: boolean, isSearchable: boolean, duplicateCriteria?: Array> | null, indexMetadataList: Array<{ __typename?: 'Index', id: string, createdAt: string, updatedAt: string, name: string, indexWhereClause?: string | null, indexType: IndexType, isUnique: boolean, isCustom?: boolean | null, indexFieldMetadataList: Array<{ __typename?: 'IndexField', id: string, fieldMetadataId: string, createdAt: string, updatedAt: string, order: number }> }>, fieldsList: Array<{ __typename?: 'Field', id: string, type: FieldMetadataType, name: string, label: string, description?: string | null, icon?: string | null, isCustom?: boolean | null, isActive?: boolean | null, isSystem?: boolean | null, isUIReadOnly?: boolean | null, isNullable?: boolean | null, isUnique?: boolean | null, createdAt: string, updatedAt: string, defaultValue?: any | null, options?: any | null, settings?: any | null, isLabelSyncedWithName?: boolean | null, relation?: { __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } } | null, morphRelations?: Array<{ __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } }> | null }> }>, serverlessFunctions: Array<{ __typename?: 'ServerlessFunction', id: string, name: string, description?: string | null, runtime: string, timeoutSeconds: number, latestVersion?: string | null, publishedVersions: Array, createdAt: string, updatedAt: string, cronTriggers?: Array<{ __typename?: 'CronTrigger', id: string, settings: any, createdAt: string, updatedAt: string }> | null, databaseEventTriggers?: Array<{ __typename?: 'DatabaseEventTrigger', id: string, settings: any, createdAt: string, updatedAt: string }> | null, routeTriggers?: Array<{ __typename?: 'RouteTrigger', id: string, path: string, isAuthRequired: boolean, httpMethod: HttpMethod, createdAt: string, updatedAt: string }> | null }> } }; + export type UploadFileMutationVariables = Exact<{ file: Scalars['Upload']; fileFolder?: InputMaybe; @@ -5132,6 +5165,8 @@ export type DeleteFileMutationVariables = Exact<{ export type DeleteFileMutation = { __typename?: 'Mutation', deleteFile: { __typename?: 'File', id: string, name: string, fullPath: string, size: number, type: string, createdAt: string } }; +export type ObjectMetadataFieldsFragment = { __typename?: 'Object', id: string, nameSingular: string, namePlural: string, labelSingular: string, labelPlural: string, description?: string | null, icon?: string | null, isCustom: boolean, isRemote: boolean, isActive: boolean, isSystem: boolean, isUIReadOnly: boolean, createdAt: string, updatedAt: string, labelIdentifierFieldMetadataId?: string | null, imageIdentifierFieldMetadataId?: string | null, applicationId?: string | null, shortcut?: string | null, isLabelSyncedWithName: boolean, isSearchable: boolean, duplicateCriteria?: Array> | null, indexMetadataList: Array<{ __typename?: 'Index', id: string, createdAt: string, updatedAt: string, name: string, indexWhereClause?: string | null, indexType: IndexType, isUnique: boolean, isCustom?: boolean | null, indexFieldMetadataList: Array<{ __typename?: 'IndexField', id: string, fieldMetadataId: string, createdAt: string, updatedAt: string, order: number }> }>, fieldsList: Array<{ __typename?: 'Field', id: string, type: FieldMetadataType, name: string, label: string, description?: string | null, icon?: string | null, isCustom?: boolean | null, isActive?: boolean | null, isSystem?: boolean | null, isUIReadOnly?: boolean | null, isNullable?: boolean | null, isUnique?: boolean | null, createdAt: string, updatedAt: string, defaultValue?: any | null, options?: any | null, settings?: any | null, isLabelSyncedWithName?: boolean | null, relation?: { __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } } | null, morphRelations?: Array<{ __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } }> | null }> }; + export type CreateOneObjectMetadataItemMutationVariables = Exact<{ input: CreateOneObjectInput; }>; @@ -5179,7 +5214,7 @@ export type DeleteOneFieldMetadataItemMutation = { __typename?: 'Mutation', dele export type ObjectMetadataItemsQueryVariables = Exact<{ [key: string]: never; }>; -export type ObjectMetadataItemsQuery = { __typename?: 'Query', objects: { __typename?: 'ObjectConnection', edges: Array<{ __typename?: 'ObjectEdge', node: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string, labelSingular: string, labelPlural: string, description?: string | null, icon?: string | null, isCustom: boolean, isRemote: boolean, isActive: boolean, isSystem: boolean, isUIReadOnly: boolean, createdAt: string, updatedAt: string, labelIdentifierFieldMetadataId?: string | null, imageIdentifierFieldMetadataId?: string | null, shortcut?: string | null, isLabelSyncedWithName: boolean, isSearchable: boolean, duplicateCriteria?: Array> | null, indexMetadataList: Array<{ __typename?: 'Index', id: string, createdAt: string, updatedAt: string, name: string, indexWhereClause?: string | null, indexType: IndexType, isUnique: boolean, isCustom?: boolean | null, indexFieldMetadataList: Array<{ __typename?: 'IndexField', id: string, fieldMetadataId: string, createdAt: string, updatedAt: string, order: number }> }>, fieldsList: Array<{ __typename?: 'Field', id: string, type: FieldMetadataType, name: string, label: string, description?: string | null, icon?: string | null, isCustom?: boolean | null, isActive?: boolean | null, isSystem?: boolean | null, isUIReadOnly?: boolean | null, isNullable?: boolean | null, isUnique?: boolean | null, createdAt: string, updatedAt: string, defaultValue?: any | null, options?: any | null, settings?: any | null, isLabelSyncedWithName?: boolean | null, relation?: { __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } } | null, morphRelations?: Array<{ __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } }> | null }> } }>, pageInfo: { __typename?: 'PageInfo', hasNextPage?: boolean | null, hasPreviousPage?: boolean | null, startCursor?: any | null, endCursor?: any | null } } }; +export type ObjectMetadataItemsQuery = { __typename?: 'Query', objects: { __typename?: 'ObjectConnection', edges: Array<{ __typename?: 'ObjectEdge', node: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string, labelSingular: string, labelPlural: string, description?: string | null, icon?: string | null, isCustom: boolean, isRemote: boolean, isActive: boolean, isSystem: boolean, isUIReadOnly: boolean, createdAt: string, updatedAt: string, labelIdentifierFieldMetadataId?: string | null, imageIdentifierFieldMetadataId?: string | null, applicationId?: string | null, shortcut?: string | null, isLabelSyncedWithName: boolean, isSearchable: boolean, duplicateCriteria?: Array> | null, indexMetadataList: Array<{ __typename?: 'Index', id: string, createdAt: string, updatedAt: string, name: string, indexWhereClause?: string | null, indexType: IndexType, isUnique: boolean, isCustom?: boolean | null, indexFieldMetadataList: Array<{ __typename?: 'IndexField', id: string, fieldMetadataId: string, createdAt: string, updatedAt: string, order: number }> }>, fieldsList: Array<{ __typename?: 'Field', id: string, type: FieldMetadataType, name: string, label: string, description?: string | null, icon?: string | null, isCustom?: boolean | null, isActive?: boolean | null, isSystem?: boolean | null, isUIReadOnly?: boolean | null, isNullable?: boolean | null, isUnique?: boolean | null, createdAt: string, updatedAt: string, defaultValue?: any | null, options?: any | null, settings?: any | null, isLabelSyncedWithName?: boolean | null, relation?: { __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } } | null, morphRelations?: Array<{ __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } }> | null }> } }>, pageInfo: { __typename?: 'PageInfo', hasNextPage?: boolean | null, hasPreviousPage?: boolean | null, startCursor?: any | null, endCursor?: any | null } } }; export type SkipBookOnboardingStepMutationVariables = Exact<{ [key: string]: never; }>; @@ -5492,7 +5527,7 @@ export type UpsertPermissionFlagsMutation = { __typename?: 'Mutation', upsertPer 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, 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, createdAt: string, updatedAt: string }>, apiKeys: Array<{ __typename?: 'ApiKeyForRole', id: string, name: string, expiresAt: string, revokedAt?: string | null }>, permissionFlags?: Array<{ __typename?: 'PermissionFlag', 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 }> | null, fieldPermissions?: Array<{ __typename?: 'FieldPermission', objectMetadataId: string, fieldMetadataId: string, canReadFieldValue?: boolean | null, canUpdateFieldValue?: boolean | null, id: string, roleId: 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, 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, applicationId?: string | null, createdAt: string, updatedAt: string }>, apiKeys: Array<{ __typename?: 'ApiKeyForRole', id: string, name: string, expiresAt: string, revokedAt?: string | null }>, permissionFlags?: Array<{ __typename?: 'PermissionFlag', 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 }> | null, fieldPermissions?: Array<{ __typename?: 'FieldPermission', objectMetadataId: string, fieldMetadataId: string, canReadFieldValue?: boolean | null, canUpdateFieldValue?: boolean | null, id: string, roleId: string }> | null }> }; export type CreateApprovedAccessDomainMutationVariables = Exact<{ input: CreateApprovedAccessDomainInput; @@ -5553,21 +5588,21 @@ export type GetSsoIdentityProvidersQueryVariables = Exact<{ [key: string]: never export type GetSsoIdentityProvidersQuery = { __typename?: 'Query', getSSOIdentityProviders: Array<{ __typename?: 'FindAvailableSSOIDPOutput', type: IdentityProviderType, id: string, name: string, issuer: string, status: SsoIdentityProviderStatus }> }; -export type ServerlessFunctionFieldsFragment = { __typename?: 'ServerlessFunction', id: string, name: string, description?: string | null, runtime: string, timeoutSeconds: number, latestVersion?: string | null, publishedVersions: Array, createdAt: string, updatedAt: string }; +export type ServerlessFunctionFieldsFragment = { __typename?: 'ServerlessFunction', id: string, name: string, description?: string | null, runtime: string, timeoutSeconds: number, latestVersion?: string | null, publishedVersions: Array, createdAt: string, updatedAt: string, cronTriggers?: Array<{ __typename?: 'CronTrigger', id: string, settings: any, createdAt: string, updatedAt: string }> | null, databaseEventTriggers?: Array<{ __typename?: 'DatabaseEventTrigger', id: string, settings: any, createdAt: string, updatedAt: string }> | null, routeTriggers?: Array<{ __typename?: 'RouteTrigger', id: string, path: string, isAuthRequired: boolean, httpMethod: HttpMethod, createdAt: string, updatedAt: string }> | null }; export type CreateOneServerlessFunctionItemMutationVariables = Exact<{ input: CreateServerlessFunctionInput; }>; -export type CreateOneServerlessFunctionItemMutation = { __typename?: 'Mutation', createOneServerlessFunction: { __typename?: 'ServerlessFunction', id: string, name: string, description?: string | null, runtime: string, timeoutSeconds: number, latestVersion?: string | null, publishedVersions: Array, createdAt: string, updatedAt: string } }; +export type CreateOneServerlessFunctionItemMutation = { __typename?: 'Mutation', createOneServerlessFunction: { __typename?: 'ServerlessFunction', id: string, name: string, description?: string | null, runtime: string, timeoutSeconds: number, latestVersion?: string | null, publishedVersions: Array, createdAt: string, updatedAt: string, cronTriggers?: Array<{ __typename?: 'CronTrigger', id: string, settings: any, createdAt: string, updatedAt: string }> | null, databaseEventTriggers?: Array<{ __typename?: 'DatabaseEventTrigger', id: string, settings: any, createdAt: string, updatedAt: string }> | null, routeTriggers?: Array<{ __typename?: 'RouteTrigger', id: string, path: string, isAuthRequired: boolean, httpMethod: HttpMethod, createdAt: string, updatedAt: string }> | null } }; export type DeleteOneServerlessFunctionMutationVariables = Exact<{ input: ServerlessFunctionIdInput; }>; -export type DeleteOneServerlessFunctionMutation = { __typename?: 'Mutation', deleteOneServerlessFunction: { __typename?: 'ServerlessFunction', id: string, name: string, description?: string | null, runtime: string, timeoutSeconds: number, latestVersion?: string | null, publishedVersions: Array, createdAt: string, updatedAt: string } }; +export type DeleteOneServerlessFunctionMutation = { __typename?: 'Mutation', deleteOneServerlessFunction: { __typename?: 'ServerlessFunction', id: string, name: string, description?: string | null, runtime: string, timeoutSeconds: number, latestVersion?: string | null, publishedVersions: Array, createdAt: string, updatedAt: string, cronTriggers?: Array<{ __typename?: 'CronTrigger', id: string, settings: any, createdAt: string, updatedAt: string }> | null, databaseEventTriggers?: Array<{ __typename?: 'DatabaseEventTrigger', id: string, settings: any, createdAt: string, updatedAt: string }> | null, routeTriggers?: Array<{ __typename?: 'RouteTrigger', id: string, path: string, isAuthRequired: boolean, httpMethod: HttpMethod, createdAt: string, updatedAt: string }> | null } }; export type ExecuteOneServerlessFunctionMutationVariables = Exact<{ input: ExecuteServerlessFunctionInput; @@ -5581,14 +5616,14 @@ export type PublishOneServerlessFunctionMutationVariables = Exact<{ }>; -export type PublishOneServerlessFunctionMutation = { __typename?: 'Mutation', publishServerlessFunction: { __typename?: 'ServerlessFunction', id: string, name: string, description?: string | null, runtime: string, timeoutSeconds: number, latestVersion?: string | null, publishedVersions: Array, createdAt: string, updatedAt: string } }; +export type PublishOneServerlessFunctionMutation = { __typename?: 'Mutation', publishServerlessFunction: { __typename?: 'ServerlessFunction', id: string, name: string, description?: string | null, runtime: string, timeoutSeconds: number, latestVersion?: string | null, publishedVersions: Array, createdAt: string, updatedAt: string, cronTriggers?: Array<{ __typename?: 'CronTrigger', id: string, settings: any, createdAt: string, updatedAt: string }> | null, databaseEventTriggers?: Array<{ __typename?: 'DatabaseEventTrigger', id: string, settings: any, createdAt: string, updatedAt: string }> | null, routeTriggers?: Array<{ __typename?: 'RouteTrigger', id: string, path: string, isAuthRequired: boolean, httpMethod: HttpMethod, createdAt: string, updatedAt: string }> | null } }; export type UpdateOneServerlessFunctionMutationVariables = Exact<{ input: UpdateServerlessFunctionInput; }>; -export type UpdateOneServerlessFunctionMutation = { __typename?: 'Mutation', updateOneServerlessFunction: { __typename?: 'ServerlessFunction', id: string, name: string, description?: string | null, runtime: string, timeoutSeconds: number, latestVersion?: string | null, publishedVersions: Array, createdAt: string, updatedAt: string } }; +export type UpdateOneServerlessFunctionMutation = { __typename?: 'Mutation', updateOneServerlessFunction: { __typename?: 'ServerlessFunction', id: string, name: string, description?: string | null, runtime: string, timeoutSeconds: number, latestVersion?: string | null, publishedVersions: Array, createdAt: string, updatedAt: string, cronTriggers?: Array<{ __typename?: 'CronTrigger', id: string, settings: any, createdAt: string, updatedAt: string }> | null, databaseEventTriggers?: Array<{ __typename?: 'DatabaseEventTrigger', id: string, settings: any, createdAt: string, updatedAt: string }> | null, routeTriggers?: Array<{ __typename?: 'RouteTrigger', id: string, path: string, isAuthRequired: boolean, httpMethod: HttpMethod, createdAt: string, updatedAt: string }> | null } }; export type FindManyAvailablePackagesQueryVariables = Exact<{ input: ServerlessFunctionIdInput; @@ -5600,14 +5635,14 @@ export type FindManyAvailablePackagesQuery = { __typename?: 'Query', getAvailabl export type GetManyServerlessFunctionsQueryVariables = Exact<{ [key: string]: never; }>; -export type GetManyServerlessFunctionsQuery = { __typename?: 'Query', findManyServerlessFunctions: Array<{ __typename?: 'ServerlessFunction', id: string, name: string, description?: string | null, runtime: string, timeoutSeconds: number, latestVersion?: string | null, publishedVersions: Array, createdAt: string, updatedAt: string }> }; +export type GetManyServerlessFunctionsQuery = { __typename?: 'Query', findManyServerlessFunctions: Array<{ __typename?: 'ServerlessFunction', id: string, name: string, description?: string | null, runtime: string, timeoutSeconds: number, latestVersion?: string | null, publishedVersions: Array, createdAt: string, updatedAt: string, cronTriggers?: Array<{ __typename?: 'CronTrigger', id: string, settings: any, createdAt: string, updatedAt: string }> | null, databaseEventTriggers?: Array<{ __typename?: 'DatabaseEventTrigger', id: string, settings: any, createdAt: string, updatedAt: string }> | null, routeTriggers?: Array<{ __typename?: 'RouteTrigger', id: string, path: string, isAuthRequired: boolean, httpMethod: HttpMethod, createdAt: string, updatedAt: string }> | null }> }; export type GetOneServerlessFunctionQueryVariables = Exact<{ input: ServerlessFunctionIdInput; }>; -export type GetOneServerlessFunctionQuery = { __typename?: 'Query', findOneServerlessFunction: { __typename?: 'ServerlessFunction', id: string, name: string, description?: string | null, runtime: string, timeoutSeconds: number, latestVersion?: string | null, publishedVersions: Array, createdAt: string, updatedAt: string } }; +export type GetOneServerlessFunctionQuery = { __typename?: 'Query', findOneServerlessFunction: { __typename?: 'ServerlessFunction', id: string, name: string, description?: string | null, runtime: string, timeoutSeconds: number, latestVersion?: string | null, publishedVersions: Array, createdAt: string, updatedAt: string, cronTriggers?: Array<{ __typename?: 'CronTrigger', id: string, settings: any, createdAt: string, updatedAt: string }> | null, databaseEventTriggers?: Array<{ __typename?: 'DatabaseEventTrigger', id: string, settings: any, createdAt: string, updatedAt: string }> | null, routeTriggers?: Array<{ __typename?: 'RouteTrigger', id: string, path: string, isAuthRequired: boolean, httpMethod: HttpMethod, createdAt: string, updatedAt: string }> | null } }; export type FindOneServerlessFunctionSourceCodeQueryVariables = Exact<{ input: GetServerlessFunctionSourceCodeInput; @@ -6103,10 +6138,166 @@ export const AgentFieldsFragmentDoc = gql` roleId isCustom modelConfiguration + applicationId createdAt updatedAt } `; +export const ObjectMetadataFieldsFragmentDoc = gql` + fragment ObjectMetadataFields on Object { + id + nameSingular + namePlural + labelSingular + labelPlural + description + icon + isCustom + isRemote + isActive + isSystem + isUIReadOnly + createdAt + updatedAt + labelIdentifierFieldMetadataId + imageIdentifierFieldMetadataId + applicationId + shortcut + isLabelSyncedWithName + isSearchable + duplicateCriteria + indexMetadataList { + id + createdAt + updatedAt + name + indexWhereClause + indexType + isUnique + isCustom + indexFieldMetadataList { + id + fieldMetadataId + createdAt + updatedAt + order + } + } + fieldsList { + id + type + name + label + description + icon + isCustom + isActive + isSystem + isUIReadOnly + isNullable + isUnique + createdAt + updatedAt + defaultValue + options + settings + isLabelSyncedWithName + relation { + type + sourceObjectMetadata { + id + nameSingular + namePlural + } + targetObjectMetadata { + id + nameSingular + namePlural + } + sourceFieldMetadata { + id + name + } + targetFieldMetadata { + id + name + } + } + morphRelations { + type + sourceObjectMetadata { + id + nameSingular + namePlural + } + targetObjectMetadata { + id + nameSingular + namePlural + } + sourceFieldMetadata { + id + name + } + targetFieldMetadata { + id + name + } + } + } +} + `; +export const ServerlessFunctionFieldsFragmentDoc = gql` + fragment ServerlessFunctionFields on ServerlessFunction { + id + name + description + runtime + timeoutSeconds + latestVersion + publishedVersions + cronTriggers { + id + settings + createdAt + updatedAt + } + databaseEventTriggers { + id + settings + createdAt + updatedAt + } + routeTriggers { + id + path + isAuthRequired + httpMethod + createdAt + updatedAt + } + createdAt + updatedAt +} + `; +export const ApplicationFieldsFragmentDoc = gql` + fragment ApplicationFields on Application { + id + name + description + agents { + ...AgentFields + } + objects { + ...ObjectMetadataFields + } + serverlessFunctions { + ...ServerlessFunctionFields + } +} + ${AgentFieldsFragmentDoc} +${ObjectMetadataFieldsFragmentDoc} +${ServerlessFunctionFieldsFragmentDoc}`; export const AuthTokenFragmentFragmentDoc = gql` fragment AuthTokenFragment on AuthToken { token @@ -6226,19 +6417,6 @@ export const PermissionFlagFragmentFragmentDoc = gql` roleId } `; -export const ServerlessFunctionFieldsFragmentDoc = gql` - fragment ServerlessFunctionFields on ServerlessFunction { - id - name - description - runtime - timeoutSeconds - latestVersion - publishedVersions - createdAt - updatedAt -} - `; export const WorkspaceMemberQueryFragmentFragmentDoc = gql` fragment WorkspaceMemberQueryFragment on WorkspaceMember { id @@ -7144,6 +7322,77 @@ export function useTrackAnalyticsMutation(baseOptions?: Apollo.MutationHookOptio export type TrackAnalyticsMutationHookResult = ReturnType; export type TrackAnalyticsMutationResult = Apollo.MutationResult; export type TrackAnalyticsMutationOptions = Apollo.BaseMutationOptions; +export const FindManyApplicationsDocument = gql` + query FindManyApplications { + findManyApplications { + id + name + description + } +} + `; + +/** + * __useFindManyApplicationsQuery__ + * + * To run a query within a React component, call `useFindManyApplicationsQuery` and pass it any options that fit your needs. + * When your component renders, `useFindManyApplicationsQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useFindManyApplicationsQuery({ + * variables: { + * }, + * }); + */ +export function useFindManyApplicationsQuery(baseOptions?: Apollo.QueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(FindManyApplicationsDocument, options); + } +export function useFindManyApplicationsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(FindManyApplicationsDocument, options); + } +export type FindManyApplicationsQueryHookResult = ReturnType; +export type FindManyApplicationsLazyQueryHookResult = ReturnType; +export type FindManyApplicationsQueryResult = Apollo.QueryResult; +export const FindOneApplicationDocument = gql` + query FindOneApplication($id: UUID!) { + findOneApplication(id: $id) { + ...ApplicationFields + } +} + ${ApplicationFieldsFragmentDoc}`; + +/** + * __useFindOneApplicationQuery__ + * + * To run a query within a React component, call `useFindOneApplicationQuery` and pass it any options that fit your needs. + * When your component renders, `useFindOneApplicationQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useFindOneApplicationQuery({ + * variables: { + * id: // value for 'id' + * }, + * }); + */ +export function useFindOneApplicationQuery(baseOptions: Apollo.QueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(FindOneApplicationDocument, options); + } +export function useFindOneApplicationLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(FindOneApplicationDocument, options); + } +export type FindOneApplicationQueryHookResult = ReturnType; +export type FindOneApplicationLazyQueryHookResult = ReturnType; +export type FindOneApplicationQueryResult = Apollo.QueryResult; export const UploadFileDocument = gql` mutation uploadFile($file: Upload!, $fileFolder: FileFolder) { uploadFile(file: $file, fileFolder: $fileFolder) { @@ -9285,105 +9534,7 @@ export const ObjectMetadataItemsDocument = gql` objects(paging: {first: 1000}) { edges { node { - id - nameSingular - namePlural - labelSingular - labelPlural - description - icon - isCustom - isRemote - isActive - isSystem - isUIReadOnly - createdAt - updatedAt - labelIdentifierFieldMetadataId - imageIdentifierFieldMetadataId - shortcut - isLabelSyncedWithName - isSearchable - duplicateCriteria - indexMetadataList { - id - createdAt - updatedAt - name - indexWhereClause - indexType - isUnique - isCustom - indexFieldMetadataList { - id - fieldMetadataId - createdAt - updatedAt - order - } - } - fieldsList { - id - type - name - label - description - icon - isCustom - isActive - isSystem - isUIReadOnly - isNullable - isUnique - createdAt - updatedAt - defaultValue - options - settings - isLabelSyncedWithName - relation { - type - sourceObjectMetadata { - id - nameSingular - namePlural - } - targetObjectMetadata { - id - nameSingular - namePlural - } - sourceFieldMetadata { - id - name - } - targetFieldMetadata { - id - name - } - } - morphRelations { - type - sourceObjectMetadata { - id - nameSingular - namePlural - } - targetObjectMetadata { - id - nameSingular - namePlural - } - sourceFieldMetadata { - id - name - } - targetFieldMetadata { - id - name - } - } - } + ...ObjectMetadataFields } } pageInfo { @@ -9394,7 +9545,7 @@ export const ObjectMetadataItemsDocument = gql` } } } - `; + ${ObjectMetadataFieldsFragmentDoc}`; /** * __useObjectMetadataItemsQuery__ diff --git a/packages/twenty-front/src/generated/graphql.ts b/packages/twenty-front/src/generated/graphql.ts index 51abfe047d..c7c1cf9eaa 100644 --- a/packages/twenty-front/src/generated/graphql.ts +++ b/packages/twenty-front/src/generated/graphql.ts @@ -51,6 +51,7 @@ export type AdminPanelWorkerQueueHealth = { export type Agent = { __typename?: 'Agent'; + applicationId?: Maybe; createdAt: Scalars['DateTime']; description?: Maybe; icon?: Maybe; @@ -200,6 +201,16 @@ export type AppTokenEdge = { node: AppToken; }; +export type Application = { + __typename?: 'Application'; + agents: Array; + description: Scalars['String']; + id: Scalars['UUID']; + name: Scalars['String']; + objects: Array; + serverlessFunctions: Array; +}; + export type ApprovedAccessDomain = { __typename?: 'ApprovedAccessDomain'; createdAt: Scalars['DateTime']; @@ -936,7 +947,6 @@ export type CronTrigger = { __typename?: 'CronTrigger'; createdAt: Scalars['DateTime']; id: Scalars['UUID']; - serverlessFunctionId: Scalars['String']; settings: Scalars['JSON']; updatedAt: Scalars['DateTime']; }; @@ -970,7 +980,6 @@ export type DatabaseEventTrigger = { __typename?: 'DatabaseEventTrigger'; createdAt: Scalars['DateTime']; id: Scalars['UUID']; - serverlessFunctionId: Scalars['String']; settings: Scalars['JSON']; updatedAt: Scalars['DateTime']; }; @@ -1190,6 +1199,7 @@ export type FeatureFlagDto = { export enum FeatureFlagKey { IS_AIRTABLE_INTEGRATION_ENABLED = 'IS_AIRTABLE_INTEGRATION_ENABLED', IS_AI_ENABLED = 'IS_AI_ENABLED', + IS_APPLICATION_ENABLED = 'IS_APPLICATION_ENABLED', IS_CALENDAR_VIEW_ENABLED = 'IS_CALENDAR_VIEW_ENABLED', IS_COMMON_API_ENABLED = 'IS_COMMON_API_ENABLED', IS_CORE_VIEW_ENABLED = 'IS_CORE_VIEW_ENABLED', @@ -2668,6 +2678,7 @@ export type NumberChartConfiguration = { export type Object = { __typename?: 'Object'; + applicationId?: Maybe; createdAt: Scalars['DateTime']; description?: Maybe; duplicateCriteria?: Maybe>>; @@ -2981,12 +2992,14 @@ export type Query = { findAgentHandoffTargets: Array; findAgentHandoffs: Array; findManyAgents: Array; + findManyApplications: Array; findManyCronTriggers: Array; findManyDatabaseEventTriggers: Array; findManyPublicDomains: Array; findManyRouteTriggers: Array; findManyServerlessFunctions: Array; findOneAgent: Agent; + findOneApplication: Application; findOneCronTrigger: CronTrigger; findOneDatabaseEventTrigger: DatabaseEventTrigger; findOneRouteTrigger: RouteTrigger; @@ -3098,6 +3111,11 @@ export type QueryFindOneAgentArgs = { }; +export type QueryFindOneApplicationArgs = { + id: Scalars['UUID']; +}; + + export type QueryFindOneCronTriggerArgs = { input: CronTriggerIdInput; }; @@ -3442,9 +3460,7 @@ export type RouteTrigger = { id: Scalars['ID']; isAuthRequired: Scalars['Boolean']; path: Scalars['String']; - serverlessFunctionId: Scalars['String']; updatedAt: Scalars['DateTime']; - workspaceId: Scalars['String']; }; export type RouteTriggerIdInput = { @@ -3530,11 +3546,14 @@ export type Sentry = { export type ServerlessFunction = { __typename?: 'ServerlessFunction'; createdAt: Scalars['DateTime']; + cronTriggers?: Maybe>; + databaseEventTriggers?: Maybe>; description?: Maybe; id: Scalars['UUID']; latestVersion?: Maybe; name: Scalars['String']; publishedVersions: Array; + routeTriggers?: Maybe>; runtime: Scalars['String']; timeoutSeconds: Scalars['Float']; updatedAt: Scalars['DateTime']; diff --git a/packages/twenty-front/src/modules/ai/graphql/fragments/agentFragment.ts b/packages/twenty-front/src/modules/ai/graphql/fragments/agentFragment.ts index e1935b9108..a01092f553 100644 --- a/packages/twenty-front/src/modules/ai/graphql/fragments/agentFragment.ts +++ b/packages/twenty-front/src/modules/ai/graphql/fragments/agentFragment.ts @@ -13,6 +13,7 @@ export const AGENT_FRAGMENT = gql` roleId isCustom modelConfiguration + applicationId createdAt updatedAt } diff --git a/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx b/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx index 548b8f6a5c..76e6bfb8bd 100644 --- a/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx +++ b/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx @@ -5,7 +5,7 @@ import { SettingsProtectedRouteWrapper } from '@/settings/components/SettingsPro import { SettingsSkeletonLoader } from '@/settings/components/SettingsSkeletonLoader'; import { SettingPublicDomain } from '@/settings/domains/components/SettingPublicDomain'; import { SettingsPath } from 'twenty-shared/types'; -import { PermissionFlagType } from '~/generated/graphql'; +import { FeatureFlagKey, PermissionFlagType } from '~/generated/graphql'; const SettingsGraphQLPlayground = lazy(() => import( @@ -105,12 +105,6 @@ const SettingsDevelopersApiKeysNew = lazy(() => })), ); -const SettingsServerlessFunctions = lazy(() => - import( - '~/pages/settings/serverless-functions/SettingsServerlessFunctions' - ).then((module) => ({ default: module.SettingsServerlessFunctions })), -); - const SettingsServerlessFunctionDetail = lazy(() => import( '~/pages/settings/serverless-functions/SettingsServerlessFunctionDetail' @@ -119,14 +113,6 @@ const SettingsServerlessFunctionDetail = lazy(() => })), ); -const SettingsServerlessFunctionsNew = lazy(() => - import( - '~/pages/settings/serverless-functions/SettingsServerlessFunctionsNew' - ).then((module) => ({ - default: module.SettingsServerlessFunctionsNew, - })), -); - const SettingsWorkspace = lazy(() => import('~/pages/settings/SettingsWorkspace').then((module) => ({ default: module.SettingsWorkspace, @@ -157,6 +143,22 @@ const SettingsAI = lazy(() => })), ); +const SettingsApplications = lazy(() => + import('~/pages/settings/applications/SettingsApplications').then( + (module) => ({ + default: module.SettingsApplications, + }), + ), +); + +const SettingsApplicationDetails = lazy(() => + import('~/pages/settings/applications/SettingsApplicationDetails').then( + (module) => ({ + default: module.SettingsApplicationDetails, + }), + ), +); + const SettingsAgentForm = lazy(() => import('~/pages/settings/ai/SettingsAgentForm').then((module) => ({ default: module.SettingsAgentForm, @@ -399,10 +401,7 @@ type SettingsRoutesProps = { isAdminPageEnabled?: boolean; }; -export const SettingsRoutes = ({ - isFunctionSettingsEnabled, - isAdminPageEnabled, -}: SettingsRoutesProps) => ( +export const SettingsRoutes = ({ isAdminPageEnabled }: SettingsRoutesProps) => ( }> } /> @@ -590,22 +589,27 @@ export const SettingsRoutes = ({ element={} /> - {isFunctionSettingsEnabled && ( - <> - } + + - } - /> - } - /> - - )} + } + > + } + /> + } + /> + } + /> + { expect(result).toBe(true); }); + + it('should return true if object is managed by application', () => { + const result = isObjectMetadataReadOnly({ + objectMetadataItem: { + applicationId: 'applicationId', + isUIReadOnly: false, + isRemote: false, + }, + }); + + expect(result).toBe(true); + }); + + it('should return false if object is custom', () => { + const result = isObjectMetadataReadOnly({ + objectMetadataItem: { + applicationId: undefined, + isUIReadOnly: false, + isRemote: false, + }, + }); + + expect(result).toBe(false); + }); }); diff --git a/packages/twenty-front/src/modules/object-record/read-only/utils/isObjectMetadataReadOnly.ts b/packages/twenty-front/src/modules/object-record/read-only/utils/isObjectMetadataReadOnly.ts index e2a936b970..39fe559e90 100644 --- a/packages/twenty-front/src/modules/object-record/read-only/utils/isObjectMetadataReadOnly.ts +++ b/packages/twenty-front/src/modules/object-record/read-only/utils/isObjectMetadataReadOnly.ts @@ -1,9 +1,13 @@ import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { type ObjectPermission } from '~/generated/graphql'; +import { isDefined } from 'twenty-shared/utils'; type IsObjectMetadataReadOnlyParams = { - objectPermissions: ObjectPermission; - objectMetadataItem: Pick; + objectPermissions?: ObjectPermission; + objectMetadataItem?: Pick< + ObjectMetadataItem, + 'isUIReadOnly' | 'isRemote' | 'applicationId' + >; }; export const isObjectMetadataReadOnly = ({ @@ -11,8 +15,10 @@ export const isObjectMetadataReadOnly = ({ objectMetadataItem, }: IsObjectMetadataReadOnlyParams) => { return ( - !objectPermissions.canUpdateObjectRecords || - objectMetadataItem.isUIReadOnly || - objectMetadataItem.isRemote + (isDefined(objectPermissions) && + !objectPermissions.canUpdateObjectRecords) || + objectMetadataItem?.isUIReadOnly || + objectMetadataItem?.isRemote || + isDefined(objectMetadataItem?.applicationId) ); }; diff --git a/packages/twenty-front/src/modules/serverless-functions/types/sources.type.ts b/packages/twenty-front/src/modules/serverless-functions/types/sources.type.ts new file mode 100644 index 0000000000..55873723ff --- /dev/null +++ b/packages/twenty-front/src/modules/serverless-functions/types/sources.type.ts @@ -0,0 +1 @@ +export type Sources = { [key: string]: string | Sources }; diff --git a/packages/twenty-front/src/modules/serverless-functions/utils/__tests__/computeNewSources.test.ts b/packages/twenty-front/src/modules/serverless-functions/utils/__tests__/computeNewSources.test.ts new file mode 100644 index 0000000000..0b1ee88e2d --- /dev/null +++ b/packages/twenty-front/src/modules/serverless-functions/utils/__tests__/computeNewSources.test.ts @@ -0,0 +1,146 @@ +import { computeNewSources } from '@/serverless-functions/utils/computeNewSources'; + +describe('computeNewSources', () => { + it('should compute new code input root 0', () => { + const previousCodeInput = { + 'index.ts': 'export const toto = () => {}', + }; + + const filePath = 'index.ts'; + + const value = 'export const totoUpdated = () => {}'; + + const expectedResult = { + 'index.ts': 'export const totoUpdated = () => {}', + }; + + expect( + computeNewSources({ previousCode: previousCodeInput, filePath, value }), + ).toEqual(expectedResult); + }); + + it('should compute new code input root 0 file changed', () => { + const previousCodeInput = { + '.env': 'ENV=env', + 'index.ts': 'export const toto = () => {}', + }; + + const filePath = '.env'; + + const value = 'ENV=env\nENV2=env2'; + + const expectedResult = { + '.env': 'ENV=env\nENV2=env2', + 'index.ts': 'export const toto = () => {}', + }; + + expect( + computeNewSources({ previousCode: previousCodeInput, filePath, value }), + ).toEqual(expectedResult); + }); + + it('should compute new code input root 0 with multiple files', () => { + const previousCodeInput = { + 'index.ts': 'export const toto = () => {}', + '.env': 'ENV', + }; + + const filePath = 'index.ts'; + + const value = 'export const totoUpdated = () => {}'; + + const expectedResult = { + 'index.ts': 'export const totoUpdated = () => {}', + '.env': 'ENV', + }; + + expect( + computeNewSources({ previousCode: previousCodeInput, filePath, value }), + ).toEqual(expectedResult); + }); + + it('should compute new code input root 1', () => { + const previousCodeInput = { + src: { 'index.ts': 'export const toto = () => {}' }, + }; + + const filePath = 'src/index.ts'; + + const value = 'export const totoUpdated = () => {}'; + + const expectedResult = { + src: { 'index.ts': 'export const totoUpdated = () => {}' }, + }; + + expect( + computeNewSources({ previousCode: previousCodeInput, filePath, value }), + ).toEqual(expectedResult); + }); + + it('should compute new code input root 1 with multiple files', () => { + const previousCodeInput = { + src: { + 'index.ts': 'export const toto = () => {}', + 'index2.ts': 'export const toto2 = () => {}', + }, + }; + + const filePath = 'src/index.ts'; + + const value = 'export const totoUpdated = () => {}'; + + const expectedResult = { + src: { + 'index.ts': 'export const totoUpdated = () => {}', + 'index2.ts': 'export const toto2 = () => {}', + }, + }; + + expect( + computeNewSources({ previousCode: previousCodeInput, filePath, value }), + ).toEqual(expectedResult); + }); + + it('should compute new code input root 1 with added files', () => { + const previousCodeInput = { + src: { + 'index.ts': 'export const toto = () => {}', + }, + }; + + const filePath = 'src/index2.ts'; + + const value = 'export const toto2 = () => {}'; + + const expectedResult = { + src: { + 'index.ts': 'export const toto = () => {}', + 'index2.ts': 'export const toto2 = () => {}', + }, + }; + + expect( + computeNewSources({ previousCode: previousCodeInput, filePath, value }), + ).toEqual(expectedResult); + }); + + it('should compute new code input multiple roots', () => { + const previousCodeInput = { + '.env': 'ENV=env', + src: { 'index.ts': 'export const toto = () => {}' }, + }; + + const filePath = 'src/index.ts'; + + const value = 'export const totoUpdated = () => {}'; + + const expectedResult = { + src: { 'index.ts': 'export const totoUpdated = () => {}' }, + '.env': 'ENV=env', + }; + + expect( + computeNewSources({ previousCode: previousCodeInput, filePath, value }), + ).toEqual(expectedResult); + }); +}); diff --git a/packages/twenty-front/src/modules/serverless-functions/utils/__tests__/flattenSources.test.ts b/packages/twenty-front/src/modules/serverless-functions/utils/__tests__/flattenSources.test.ts new file mode 100644 index 0000000000..ac04f889dd --- /dev/null +++ b/packages/twenty-front/src/modules/serverless-functions/utils/__tests__/flattenSources.test.ts @@ -0,0 +1,58 @@ +// IA Generated +import { type Sources } from '@/serverless-functions/types/sources.type'; +import { flattenSources } from '@/serverless-functions/utils/flattenSources'; + +describe('flattenSources', () => { + it('flattens nested sources with root files', () => { + const input: Sources = { + '.env': 'KEY=VALUE', + src: { + 'index.ts': 'export const a = 1', + lib: { + 'util.ts': 'export const util = () => {}', + }, + }, + docs: { + 'README.md': '# Hello', + }, + }; + + const result = flattenSources(input); + + expect(result).toEqual([ + { path: '.env', content: 'KEY=VALUE' }, + { path: 'docs/README.md', content: '# Hello' }, + { path: 'src/index.ts', content: 'export const a = 1' }, + { path: 'src/lib/util.ts', content: 'export const util = () => {}' }, + ]); + }); + + it('handles deep nesting and preserves file contents', () => { + const input: Sources = { + a: { b: { c: { d: { 'file.ts': 'content' } } } }, + }; + + expect(flattenSources(input)).toEqual([ + { path: 'a/b/c/d/file.ts', content: 'content' }, + ]); + }); + + it('ignores empty folders and non-string leaves', () => { + const input: Sources = { + empty: {}, + weird: { + oops: 42, + } as unknown as Sources, + file: 'ok', + }; + + const res = flattenSources(input); + expect(res).toEqual([{ path: 'file', content: 'ok' }]); + }); + + it('accepts a custom basePath prefix', () => { + const input: Sources = { src: { 'index.ts': 'x' } }; + const res = flattenSources(input, 'pkg'); + expect(res).toEqual([{ path: 'pkg/src/index.ts', content: 'x' }]); + }); +}); diff --git a/packages/twenty-front/src/modules/serverless-functions/utils/computeNewSources.ts b/packages/twenty-front/src/modules/serverless-functions/utils/computeNewSources.ts new file mode 100644 index 0000000000..ffbf77a3b9 --- /dev/null +++ b/packages/twenty-front/src/modules/serverless-functions/utils/computeNewSources.ts @@ -0,0 +1,48 @@ +import { type Sources } from '@/serverless-functions/types/sources.type'; + +export const computeNewSources = ({ + previousCode, + filePath, + value, +}: { + previousCode: Sources; + filePath: string; + value: string; +}): Sources => { + const result = { ...previousCode }; + + const parts = filePath.split('/').filter(Boolean); + + if (parts.length === 0) { + return result; + } + + if (parts.length === 1) { + result[filePath] = value; + + return result; + } + + const [root, ...rest] = parts; + + const newFilePath = rest.join('/'); + + if ( + typeof result?.[root] === 'string' || + typeof previousCode[root] === 'string' + ) { + throw Error('Cannot compute new code input'); + } + + return { + ...previousCode, + [root]: { + ...previousCode[root], + ...computeNewSources({ + previousCode: result?.[root] ?? {}, + filePath: newFilePath, + value, + }), + }, + }; +}; diff --git a/packages/twenty-front/src/modules/serverless-functions/utils/flattenSources.ts b/packages/twenty-front/src/modules/serverless-functions/utils/flattenSources.ts new file mode 100644 index 0000000000..8aa41feaaa --- /dev/null +++ b/packages/twenty-front/src/modules/serverless-functions/utils/flattenSources.ts @@ -0,0 +1,28 @@ +// IA Generated +import { type Sources } from '@/serverless-functions/types/sources.type'; + +type FlatSource = { path: string; content: string }; + +export const flattenSources = ( + sources: Sources, + basePath = '', +): FlatSource[] => { + const out: FlatSource[] = []; + + const join = (a: string, b: string) => (a ? `${a}/${b}` : b); + + const walk = (node: Sources, prefix: string) => { + for (const [name, value] of Object.entries(node)) { + if (typeof value === 'string') { + out.push({ path: join(prefix, name), content: value }); + } else if (value && typeof value === 'object') { + walk(value as Sources, join(prefix, name)); + } + } + }; + + walk(sources, basePath); + + out.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0)); + return out; +}; diff --git a/packages/twenty-front/src/modules/settings/components/SettingsDatabaseEventsForm.tsx b/packages/twenty-front/src/modules/settings/components/SettingsDatabaseEventsForm.tsx new file mode 100644 index 0000000000..e1f1e72153 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/components/SettingsDatabaseEventsForm.tsx @@ -0,0 +1,112 @@ +import { Select } from '@/ui/input/components/Select'; +import { isDefined } from 'twenty-shared/utils'; +import { IconButton, type SelectOption } from 'twenty-ui/input'; +import { + IconBox, + IconNorthStar, + IconPlus, + IconTrash, + useIcons, +} from 'twenty-ui/display'; +import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; +import styled from '@emotion/styled'; +import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; + +const OBJECT_DROPDOWN_WIDTH = 340; +const ACTION_DROPDOWN_WIDTH = 140; +const OBJECT_MOBILE_WIDTH = 150; +const ACTION_MOBILE_WIDTH = 140; + +const StyledFilterRow = styled.div<{ isMobile: boolean }>` + display: grid; + grid-template-columns: ${({ isMobile }) => + isMobile + ? `${OBJECT_MOBILE_WIDTH}px ${ACTION_MOBILE_WIDTH}px auto` + : `${OBJECT_DROPDOWN_WIDTH}px ${ACTION_DROPDOWN_WIDTH}px auto`}; + gap: ${({ theme }) => theme.spacing(2)}; + margin-bottom: ${({ theme }) => theme.spacing(2)}; + align-items: center; +`; + +const StyledPlaceholder = styled.div` + height: ${({ theme }) => theme.spacing(8)}; + width: ${({ theme }) => theme.spacing(8)}; +`; + +export const SettingsDatabaseEventsForm = ({ + events, + updateOperation, + removeOperation, + disabled = false, +}: { + events: { object: string | null; action: string }[]; + updateOperation?: ( + index: number, + field: 'object' | 'action', + value: string | null, + ) => void; + removeOperation?: (index: number) => void; + disabled?: boolean; +}) => { + const isMobile = useIsMobile(); + + const { objectMetadataItems } = useObjectMetadataItems(); + + const { getIcon } = useIcons(); + + const objectOptions: SelectOption[] = [ + { label: 'All Objects', value: '*', Icon: IconNorthStar }, + ...objectMetadataItems.map((item) => ({ + label: item.labelPlural, + value: item.nameSingular, + Icon: getIcon(item.icon), + })), + ]; + + const actionOptions: SelectOption[] = [ + { label: 'All', value: '*', Icon: IconNorthStar }, + { label: 'Created', value: 'created', Icon: IconPlus }, + { label: 'Updated', value: 'updated', Icon: IconBox }, + { label: 'Deleted', value: 'deleted', Icon: IconTrash }, + ]; + + return ( + <> + {events.map((operation, index) => ( + + + updateOperation?.(index, 'action', newValue) + } + fullWidth + disabled={disabled} + /> + {isDefined(operation.object) && !disabled ? ( + removeOperation?.(index)} + /> + ) : ( + + )} + + ))} + + ); +}; diff --git a/packages/twenty-front/src/modules/settings/components/SettingsItemTypeTag.tsx b/packages/twenty-front/src/modules/settings/components/SettingsItemTypeTag.tsx new file mode 100644 index 0000000000..7a823af584 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/components/SettingsItemTypeTag.tsx @@ -0,0 +1,27 @@ +import { Tag } from 'twenty-ui/components'; +import { getItemTagInfo } from '@/settings/data-model/utils/getItemTagInfo'; + +type SettingsItemTypeTagProps = { + item: { + isCustom?: boolean; + isRemote?: boolean; + applicationId?: string | null; + }; + className?: string; +}; + +export const SettingsItemTypeTag = ({ + className, + item: { isCustom, isRemote, applicationId }, +}: SettingsItemTypeTagProps) => { + const itemTagInfo = getItemTagInfo({ isCustom, isRemote, applicationId }); + + return ( + + ); +}; diff --git a/packages/twenty-front/src/modules/settings/data-model/components/SettingsDataModelPreviewFormCard.tsx b/packages/twenty-front/src/modules/settings/data-model/components/SettingsDataModelPreviewFormCard.tsx index 27c16f2531..488e307975 100644 --- a/packages/twenty-front/src/modules/settings/data-model/components/SettingsDataModelPreviewFormCard.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/components/SettingsDataModelPreviewFormCard.tsx @@ -9,6 +9,7 @@ type SettingsDataModelPreviewFormCardProps = { className?: string; preview: ReactNode; form?: ReactNode; + disabled?: boolean; }; const StyledPreviewContainer = styled(CardContent)` diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/boolean/components/SettingsDataModelFieldBooleanForm.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/boolean/components/SettingsDataModelFieldBooleanForm.tsx index fd5c0a7b45..c3edf43f00 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/boolean/components/SettingsDataModelFieldBooleanForm.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/boolean/components/SettingsDataModelFieldBooleanForm.tsx @@ -18,9 +18,11 @@ export type SettingsDataModelFieldBooleanFormValues = z.infer< type SettingsDataModelFieldBooleanFormProps = { existingFieldMetadataId: string; + disabled?: boolean; }; export const SettingsDataModelFieldBooleanForm = ({ + disabled, existingFieldMetadataId, }: SettingsDataModelFieldBooleanFormProps) => { const { t } = useLingui(); @@ -46,6 +48,7 @@ export const SettingsDataModelFieldBooleanForm = ({ onChange={onChange} dropdownId="object-field-default-value-select-boolean" dropdownWidth={120} + disabled={disabled} needIconCheck={false} options={BOOLEAN_DATA_MODEL_SELECT_OPTIONS.map((option) => ({ ...option, diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/boolean/components/SettingsDataModelFieldBooleanSettingsFormCard.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/boolean/components/SettingsDataModelFieldBooleanSettingsFormCard.tsx index 1b1f5a44be..fc6d5338e8 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/boolean/components/SettingsDataModelFieldBooleanSettingsFormCard.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/boolean/components/SettingsDataModelFieldBooleanSettingsFormCard.tsx @@ -12,11 +12,13 @@ import { type SettingsDataModelFieldEditFormValues } from '~/pages/settings/data type SettingsDataModelFieldBooleanSettingsFormCardProps = { existingFieldMetadataId: string; objectNameSingular: string; + disabled?: boolean; }; export const SettingsDataModelFieldBooleanSettingsFormCard = ({ existingFieldMetadataId, objectNameSingular, + disabled = false, }: SettingsDataModelFieldBooleanSettingsFormCardProps) => { const { watch } = useFormContext< SettingsDataModelFieldBooleanFormValues & @@ -38,6 +40,7 @@ export const SettingsDataModelFieldBooleanSettingsFormCard = ({ } form={ } diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldIconLabelForm.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldIconLabelForm.tsx index 801987b7ff..45512b6258 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldIconLabelForm.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldIconLabelForm.tsx @@ -76,12 +76,14 @@ type SettingsDataModelFieldIconLabelFormProps = { fieldMetadataItem?: FieldMetadataItem; maxLength?: number; isCreationMode?: boolean; + readonly?: boolean; }; export const SettingsDataModelFieldIconLabelForm = ({ isCreationMode = false, fieldMetadataItem, maxLength, + readonly = false, }: SettingsDataModelFieldIconLabelFormProps) => { const { control, @@ -150,6 +152,7 @@ export const SettingsDataModelFieldIconLabelForm = ({ selectedIconKey={value ?? 'IconUsers'} onChange={({ iconKey }) => onChange(iconKey)} variant="primary" + disabled={readonly} /> )} /> @@ -162,7 +165,7 @@ export const SettingsDataModelFieldIconLabelForm = ({ instanceId={labelTextInputId} placeholder={t`Employees`} value={value} - disabled={!isLabelEditEnabled} + disabled={!isLabelEditEnabled || readonly} onChange={(value) => { onChange(value); trigger('label'); @@ -199,6 +202,7 @@ export const SettingsDataModelFieldIconLabelForm = ({ placeholder={t`employees`} value={value} onChange={onChange} + readOnly={readonly} disabled={!isNameEditEnabled} fullWidth maxLength={DATABASE_IDENTIFIER_MAXIMUM_LENGTH} @@ -245,6 +249,7 @@ export const SettingsDataModelFieldIconLabelForm = ({ title={t`Synchronize Field Label and API Name`} description={t`Should changing a field's label also change the API name?`} checked={value ?? true} + disabled={readonly} advancedMode onChange={(value) => { onChange(value); diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldIsUniqueForm.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldIsUniqueForm.tsx index d3d7c6e5d6..b16f01ab83 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldIsUniqueForm.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldIsUniqueForm.tsx @@ -18,12 +18,14 @@ type SettingsDataModelFieldIsUniqueFormProps = { objectNameSingular: string; fieldType: FieldMetadataType; existingFieldMetadataId: string; + disabled?: boolean; }; export const SettingsDataModelFieldIsUniqueForm = ({ fieldType, existingFieldMetadataId, objectNameSingular, + disabled = false, }: SettingsDataModelFieldIsUniqueFormProps) => { const { control } = useFormContext(); @@ -74,7 +76,7 @@ export const SettingsDataModelFieldIsUniqueForm = ({ toggleSize="small" value={isUnique} onChange={(value) => onChange(value)} - disabled={hasStandardUniqueIndex} + disabled={disabled || hasStandardUniqueIndex} /> diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard.tsx index 77a1aa9c00..5825202cfb 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard.tsx @@ -160,6 +160,7 @@ type SettingsDataModelFieldSettingsFormCardProps = { existingFieldMetadataId: string; fieldType: FieldMetadataType; objectNameSingular: string; + disabled?: boolean; }; const previewableTypes = [ @@ -188,6 +189,7 @@ export const SettingsDataModelFieldSettingsFormCard = ({ existingFieldMetadataId, fieldType, objectNameSingular, + disabled = false, }: SettingsDataModelFieldSettingsFormCardProps) => { const { watch } = useFormContext(); @@ -200,6 +202,7 @@ export const SettingsDataModelFieldSettingsFormCard = ({ ); } @@ -209,6 +212,7 @@ export const SettingsDataModelFieldSettingsFormCard = ({ ); } @@ -222,6 +226,7 @@ export const SettingsDataModelFieldSettingsFormCard = ({ existingFieldMetadataId={existingFieldMetadataId} fieldType={fieldType} objectNameSingular={objectNameSingular} + disabled={disabled} /> ); } @@ -231,6 +236,7 @@ export const SettingsDataModelFieldSettingsFormCard = ({ ); } @@ -240,6 +246,7 @@ export const SettingsDataModelFieldSettingsFormCard = ({ ); } @@ -249,6 +256,7 @@ export const SettingsDataModelFieldSettingsFormCard = ({ ); } @@ -258,6 +266,7 @@ export const SettingsDataModelFieldSettingsFormCard = ({ ); } @@ -267,6 +276,7 @@ export const SettingsDataModelFieldSettingsFormCard = ({ ); } @@ -276,6 +286,7 @@ export const SettingsDataModelFieldSettingsFormCard = ({ ); } @@ -289,6 +300,7 @@ export const SettingsDataModelFieldSettingsFormCard = ({ existingFieldMetadataId={existingFieldMetadataId} fieldType={fieldType} objectNameSingular={objectNameSingular} + disabled={disabled} /> ); } @@ -318,6 +330,7 @@ export const SettingsDataModelFieldSettingsFormCard = ({ @@ -326,6 +339,7 @@ export const SettingsDataModelFieldSettingsFormCard = ({ fieldType={fieldType} existingFieldMetadataId={existingFieldMetadataId} objectNameSingular={objectNameSingular} + disabled={disabled} /> } diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/text/SettingsDataModelFieldTextSettingsFormCard.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/text/SettingsDataModelFieldTextSettingsFormCard.tsx index 71702701dd..717b026193 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/text/SettingsDataModelFieldTextSettingsFormCard.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/text/SettingsDataModelFieldTextSettingsFormCard.tsx @@ -42,6 +42,7 @@ export const SettingsDataModelFieldTextSettingsFormCard = ({ fieldType={FieldMetadataType.TEXT} existingFieldMetadataId={existingFieldMetadataId} objectNameSingular={objectNameSingular} + disabled={disabled} /> } diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/date/components/SettingsDataModelFieldDateSettingsFormCard.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/date/components/SettingsDataModelFieldDateSettingsFormCard.tsx index b42f81edce..c99b91611e 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/date/components/SettingsDataModelFieldDateSettingsFormCard.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/date/components/SettingsDataModelFieldDateSettingsFormCard.tsx @@ -55,6 +55,7 @@ export const SettingsDataModelFieldDateSettingsFormCard = ({ fieldType={fieldType} existingFieldMetadataId={existingFieldMetadataId} objectNameSingular={objectNameSingular} + disabled={disabled} /> } diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/morph-relation/components/SettingsDataModelFieldMorphRelationForm.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/morph-relation/components/SettingsDataModelFieldMorphRelationForm.tsx index 61504f7c4a..d7af75e00b 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/morph-relation/components/SettingsDataModelFieldMorphRelationForm.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/morph-relation/components/SettingsDataModelFieldMorphRelationForm.tsx @@ -39,10 +39,12 @@ export type SettingsDataModelFieldMorphRelationFormValues = z.infer< type SettingsDataModelFieldMorphRelationFormProps = { existingFieldMetadataId: string; + disabled?: boolean; }; export const SettingsDataModelFieldMorphRelationForm = ({ existingFieldMetadataId, + disabled = false, }: SettingsDataModelFieldMorphRelationFormProps) => { const { t } = useLingui(); const { control } = @@ -90,7 +92,7 @@ export const SettingsDataModelFieldMorphRelationForm = ({ label={t`Relation type`} dropdownId="relation-type-select" fullWidth - disabled={disableRelationEdition} + disabled={disabled || disableRelationEdition} value={value} options={RELATION_TYPE_OPTIONS} onChange={onChange} diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/morph-relation/components/SettingsDataModelFieldMorphRelationFormCard.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/morph-relation/components/SettingsDataModelFieldMorphRelationFormCard.tsx index 3f82825bd2..5bdc22ca68 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/morph-relation/components/SettingsDataModelFieldMorphRelationFormCard.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/morph-relation/components/SettingsDataModelFieldMorphRelationFormCard.tsx @@ -18,11 +18,13 @@ import { type SettingsDataModelFieldEditFormValues } from '~/pages/settings/data type SettingsDataModelFieldMorphRelationFormCardProps = { existingFieldMetadataId: string; objectNameSingular: string; + disabled?: boolean; }; export const SettingsDataModelFieldMorphRelationFormCard = ({ existingFieldMetadataId, objectNameSingular, + disabled = false, }: SettingsDataModelFieldMorphRelationFormCardProps) => { const { watch } = useFormContext< SettingsDataModelFieldMorphRelationFormValues & @@ -107,6 +109,7 @@ export const SettingsDataModelFieldMorphRelationFormCard = ({ form={ } /> diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberSettingsFormCard.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberSettingsFormCard.tsx index b49faa4055..97c1b8ed18 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberSettingsFormCard.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberSettingsFormCard.tsx @@ -42,6 +42,7 @@ export const SettingsDataModelFieldNumberSettingsFormCard = ({ fieldType={FieldMetadataType.NUMBER} existingFieldMetadataId={existingFieldMetadataId} objectNameSingular={objectNameSingular} + disabled={disabled} /> } diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/phones/components/SettingsDataModelFieldPhonesSettingsFormCard.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/phones/components/SettingsDataModelFieldPhonesSettingsFormCard.tsx index d3a1d83c20..f2339a070e 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/phones/components/SettingsDataModelFieldPhonesSettingsFormCard.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/phones/components/SettingsDataModelFieldPhonesSettingsFormCard.tsx @@ -58,6 +58,7 @@ export const SettingsDataModelFieldPhonesSettingsFormCard = ({ fieldType={FieldMetadataType.PHONES} existingFieldMetadataId={existingFieldMetadataId} objectNameSingular={objectNameSingular} + disabled={disabled} /> } diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/relation/components/SettingsDataModelFieldRelationForm.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/relation/components/SettingsDataModelFieldRelationForm.tsx index 057e1b6272..07aee24036 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/relation/components/SettingsDataModelFieldRelationForm.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/relation/components/SettingsDataModelFieldRelationForm.tsx @@ -47,6 +47,7 @@ export type SettingsDataModelFieldRelationFormValues = z.infer< type SettingsDataModelFieldRelationFormProps = { existingFieldMetadataId: string; objectMetadataItem?: ObjectMetadataItem; + disabled?: boolean; }; export const StyledContainer = styled.div` @@ -84,6 +85,7 @@ export const RELATION_TYPE_OPTIONS = Object.entries(RELATION_TYPES).map( export const SettingsDataModelFieldRelationForm = ({ existingFieldMetadataId, objectMetadataItem, + disabled, }: SettingsDataModelFieldRelationFormProps) => { const { t } = useLingui(); const { control, watch: watchFormValue } = @@ -129,7 +131,7 @@ export const SettingsDataModelFieldRelationForm = ({ label={t`Relation type`} dropdownId="relation-type-select" fullWidth - disabled={disableRelationEdition} + disabled={disabled || disableRelationEdition} value={value} options={RELATION_TYPE_OPTIONS} onChange={onChange} @@ -145,7 +147,7 @@ export const SettingsDataModelFieldRelationForm = ({ label={t`Object destination`} dropdownId="object-destination-select" fullWidth - disabled={disableRelationEdition} + disabled={disabled || disableRelationEdition} value={value} options={activeObjectMetadataItems .filter(isObjectMetadataAvailableForRelation) @@ -175,7 +177,7 @@ export const SettingsDataModelFieldRelationForm = ({ defaultValue={initialRelationFieldMetadataItem.icon} render={({ field: { onChange, value } }) => ( onChange(iconKey)} @@ -190,7 +192,7 @@ export const SettingsDataModelFieldRelationForm = ({ render={({ field: { onChange, value } }) => ( { const { watch } = useFormContext< SettingsDataModelFieldRelationFormValues & @@ -95,6 +97,7 @@ export const SettingsDataModelFieldRelationSettingsFormCard = ({ } /> diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/components/SettingsDataModelFieldSelectForm.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/components/SettingsDataModelFieldSelectForm.tsx index a5eebe77d6..6098e1aacc 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/components/SettingsDataModelFieldSelectForm.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/components/SettingsDataModelFieldSelectForm.tsx @@ -48,6 +48,7 @@ export type SettingsDataModelFieldSelectFormValues = z.infer< type SettingsDataModelFieldSelectFormProps = { fieldType: FieldMetadataType.SELECT | FieldMetadataType.MULTI_SELECT; existingFieldMetadataId: string; + disabled?: boolean; }; const StyledContainer = styled(CardContent)` @@ -112,6 +113,7 @@ const StyledButton = styled(LightButton)` export const SettingsDataModelFieldSelectForm = ({ existingFieldMetadataId, fieldType, + disabled = false, }: SettingsDataModelFieldSelectFormProps) => { const { initialDefaultValue, initialOptions } = useSelectSettingsFormInitialValues({ @@ -281,7 +283,11 @@ export const SettingsDataModelFieldSelectForm = ({ handleDragEnd(options, result, onChange)} + onDragEnd={(result) => + !disabled + ? handleDragEnd(options, result, onChange) + : undefined + } draggableItems={ <> {options.map((option, index) => ( @@ -297,6 +303,9 @@ export const SettingsDataModelFieldSelectForm = ({ option={option} isNewRow={index === options.length - 1} onChange={(nextOption) => { + if (disabled) { + return; + } const nextOptions = toSpliced( options, index, @@ -315,6 +324,9 @@ export const SettingsDataModelFieldSelectForm = ({ } }} onRemove={() => { + if (disabled) { + return; + } const nextOptions = toSpliced( options, index, @@ -326,13 +338,24 @@ export const SettingsDataModelFieldSelectForm = ({ onChange(nextOptions); }} isDefault={isOptionDefaultValue(option.value)} - onSetAsDefault={() => - handleSetOptionAsDefault(option.value) - } - onRemoveAsDefault={() => - handleRemoveOptionAsDefault(option.value) - } - onInputEnter={handleInputEnter} + onSetAsDefault={() => { + if (disabled) { + return; + } + handleSetOptionAsDefault(option.value); + }} + onRemoveAsDefault={() => { + if (disabled) { + return; + } + handleRemoveOptionAsDefault(option.value); + }} + onInputEnter={() => { + if (disabled) { + return; + } + handleInputEnter(); + }} /> } /> @@ -341,13 +364,15 @@ export const SettingsDataModelFieldSelectForm = ({ } /> - - - + {!disabled && ( + + + + )} )} /> diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/components/SettingsDataModelFieldSelectSettingsFormCard.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/components/SettingsDataModelFieldSelectSettingsFormCard.tsx index a2b7cc76d2..71691738e4 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/components/SettingsDataModelFieldSelectSettingsFormCard.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/components/SettingsDataModelFieldSelectSettingsFormCard.tsx @@ -20,12 +20,14 @@ type SettingsDataModelFieldSelectSettingsFormCardProps = { objectNameSingular: string; fieldType: FieldMetadataType.SELECT | FieldMetadataType.MULTI_SELECT; existingFieldMetadataId: string; + disabled?: boolean; }; export const SettingsDataModelFieldSelectSettingsFormCard = ({ objectNameSingular, fieldType, existingFieldMetadataId, + disabled = false, }: SettingsDataModelFieldSelectSettingsFormCardProps) => { const { watch: watchFormValue } = useFormContext< SettingsDataModelFieldSelectOrMultiSelectFormValues & @@ -50,6 +52,7 @@ export const SettingsDataModelFieldSelectSettingsFormCard = ({ } /> diff --git a/packages/twenty-front/src/modules/settings/data-model/graph-overview/components/SettingsDataModelOverviewObject.tsx b/packages/twenty-front/src/modules/settings/data-model/graph-overview/components/SettingsDataModelOverviewObject.tsx index b39cc71bb9..6886f65ff7 100644 --- a/packages/twenty-front/src/modules/settings/data-model/graph-overview/components/SettingsDataModelOverviewObject.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/graph-overview/components/SettingsDataModelOverviewObject.tsx @@ -6,8 +6,6 @@ import { Link } from 'react-router-dom'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; import { ObjectFieldRow } from '@/settings/data-model/graph-overview/components/SettingsDataModelOverviewField'; -import { SettingsDataModelObjectTypeTag } from '@/settings/data-model/objects/components/SettingsDataModelObjectTypeTag'; -import { getObjectTypeLabel } from '@/settings/data-model/utils/getObjectTypeLabel'; import { FieldMetadataType } from '~/generated-metadata/graphql'; import { ObjectFieldRowWithoutRelation } from '@/settings/data-model/graph-overview/components/SettingsDataModelOverviewFieldWithoutRelation'; @@ -16,6 +14,7 @@ import { useState } from 'react'; import { SettingsPath } from 'twenty-shared/types'; import { getSettingsPath } from 'twenty-shared/utils'; import { IconChevronDown, IconChevronUp, useIcons } from 'twenty-ui/display'; +import { SettingsItemTypeTag } from '@/settings/components/SettingsItemTypeTag'; type SettingsDataModelOverviewObjectNode = Node; type SettingsDataModelOverviewObjectProps = @@ -134,9 +133,7 @@ export const SettingsDataModelOverviewObject = ({ · {totalCount} - + diff --git a/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldActiveActionDropdown.tsx b/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldActiveActionDropdown.tsx index e7ef66e3fe..e1a945b6bd 100644 --- a/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldActiveActionDropdown.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldActiveActionDropdown.tsx @@ -20,10 +20,12 @@ type SettingsObjectFieldActiveActionDropdownProps = { onEdit: () => void; onSetAsLabelIdentifier?: () => void; fieldMetadataItemId: string; + readonly?: boolean; }; export const SettingsObjectFieldActiveActionDropdown = ({ isCustomField, + readonly = false, onDeactivate, onEdit, onSetAsLabelIdentifier, @@ -62,18 +64,18 @@ export const SettingsObjectFieldActiveActionDropdown = ({ - {isDefined(onSetAsLabelIdentifier) && ( + {isDefined(onSetAsLabelIdentifier) && !readonly && ( )} - {isDefined(onDeactivate) && ( + {isDefined(onDeactivate) && !readonly && ( void; onDelete: () => void; fieldMetadataItemId: string; + readonly?: boolean; }; export const SettingsObjectFieldInactiveActionDropdown = ({ onActivate, + readonly = false, fieldMetadataItemId, onDelete, onEdit, @@ -66,16 +68,18 @@ export const SettingsObjectFieldInactiveActionDropdown = ({ - - {isDeletable && ( + {!readonly && ( + + )} + {isDeletable && !readonly && ( { + if (readonly) { + return; + } + await deactivateMetadataField( activeFieldMetadatItem.id, objectMetadataItem.id, @@ -146,13 +155,17 @@ export const SettingsObjectFieldItemTableRow = ({ const handleSetLabelIdentifierField = ( activeFieldMetadatItem: FieldMetadataItem, - ) => + ) => { + if (readonly) { + return; + } updateOneObjectMetadataItem({ idToUpdate: objectMetadataItem.id, updatePayload: { labelIdentifierFieldMetadataId: activeFieldMetadatItem.id, }, }); + }; const [, setActiveSettingsObjectFields] = useRecoilState( settingsObjectFieldsFamilyState({ @@ -254,6 +267,7 @@ export const SettingsObjectFieldItemTableRow = ({ mode === 'view' ? ( navigate(SettingsPath.ObjectFieldEdit, { @@ -284,6 +298,7 @@ export const SettingsObjectFieldItemTableRow = ({ ) : mode === 'view' ? ( navigate(SettingsPath.ObjectFieldEdit, { diff --git a/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectItemTableRow.tsx b/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectItemTableRow.tsx index 971fd7a2bb..84cd0cba66 100644 --- a/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectItemTableRow.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectItemTableRow.tsx @@ -3,11 +3,10 @@ import styled from '@emotion/styled'; import { type ReactNode } from 'react'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { SettingsDataModelObjectTypeTag } from '@/settings/data-model/objects/components/SettingsDataModelObjectTypeTag'; -import { getObjectTypeLabel } from '@/settings/data-model/utils/getObjectTypeLabel'; import { TableCell } from '@/ui/layout/table/components/TableCell'; import { TableRow } from '@/ui/layout/table/components/TableRow'; import { useIcons } from 'twenty-ui/display'; +import { SettingsItemTypeTag } from '@/settings/components/SettingsItemTypeTag'; export type SettingsObjectMetadataItemTableRowProps = { action: ReactNode; @@ -46,7 +45,6 @@ export const SettingsObjectMetadataItemTableRow = ({ const { getIcon } = useIcons(); const Icon = getIcon(objectMetadataItem.icon); - const objectTypeLabel = getObjectTypeLabel(objectMetadataItem); return ( @@ -63,7 +61,7 @@ export const SettingsObjectMetadataItemTableRow = ({ - + {objectMetadataItem.fields.filter((field) => !field.isSystem).length} diff --git a/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsUpdateDataModelObjectAboutForm.tsx b/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsUpdateDataModelObjectAboutForm.tsx index 0d2ce4fdc8..7cf8e4262e 100644 --- a/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsUpdateDataModelObjectAboutForm.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsUpdateDataModelObjectAboutForm.tsx @@ -14,6 +14,7 @@ import { SettingsPath } from 'twenty-shared/types'; import { ZodError } from 'zod'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; import { updatedObjectNamePluralState } from '~/pages/settings/data-model/states/updatedObjectNamePluralState'; +import { isObjectMetadataReadOnly } from '@/object-record/read-only/utils/isObjectMetadataReadOnly'; type SettingsUpdateDataModelObjectAboutFormProps = { objectMetadataItem: ObjectMetadataItem; @@ -22,6 +23,7 @@ type SettingsUpdateDataModelObjectAboutFormProps = { export const SettingsUpdateDataModelObjectAboutForm = ({ objectMetadataItem, }: SettingsUpdateDataModelObjectAboutFormProps) => { + const readonly = isObjectMetadataReadOnly({ objectMetadataItem }); const navigate = useNavigateSettings(); const { enqueueErrorSnackBar } = useSnackBar(); const setUpdatedObjectNamePlural = useSetRecoilState( @@ -54,6 +56,10 @@ export const SettingsUpdateDataModelObjectAboutForm = ({ const handleSave = async ( formValues: SettingsDataModelObjectAboutFormValues, ) => { + if (readonly) { + return; + } + if (!(Object.keys(formConfig.formState.dirtyFields).length > 0)) { return; } @@ -71,10 +77,10 @@ export const SettingsUpdateDataModelObjectAboutForm = ({ description, icon: icon ?? undefined, isLabelSyncedWithName: formValues.isLabelSyncedWithName, - labelPlural: updatedObject.data?.updateOneObject.labelPlural, - labelSingular: updatedObject.data?.updateOneObject.labelSingular, - namePlural: updatedObject.data?.updateOneObject.namePlural, - nameSingular: updatedObject.data?.updateOneObject.nameSingular, + labelPlural: updatedObject?.data?.updateOneObject.labelPlural, + labelSingular: updatedObject?.data?.updateOneObject.labelSingular, + namePlural: updatedObject?.data?.updateOneObject.namePlural, + nameSingular: updatedObject?.data?.updateOneObject.nameSingular, }); } else { formConfig.reset(undefined, { keepValues: true }); @@ -91,6 +97,10 @@ export const SettingsUpdateDataModelObjectAboutForm = ({ const updateObjectMetadata = async ( formValues: SettingsDataModelObjectAboutFormValues, ) => { + if (readonly) { + return; + } + const updatePayload = { ...formValues }; if (!objectMetadataItem.isCustom) { @@ -139,7 +149,7 @@ export const SettingsUpdateDataModelObjectAboutForm = ({ formConfig.handleSubmit(handleSave)()} - disableEdition={!objectMetadataItem.isCustom} + disableEdition={!objectMetadataItem.isCustom || readonly} objectMetadataItem={objectMetadataItem} /> diff --git a/packages/twenty-front/src/modules/settings/data-model/object-details/components/tabs/ObjectFields.tsx b/packages/twenty-front/src/modules/settings/data-model/object-details/components/tabs/ObjectFields.tsx index 08870d5e02..54c6ead8a5 100644 --- a/packages/twenty-front/src/modules/settings/data-model/object-details/components/tabs/ObjectFields.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/object-details/components/tabs/ObjectFields.tsx @@ -9,6 +9,7 @@ import { H2Title, IconPlus } from 'twenty-ui/display'; import { Button } from 'twenty-ui/input'; import { Section } from 'twenty-ui/layout'; import { UndecoratedLink } from 'twenty-ui/navigation'; +import { isObjectMetadataReadOnly } from '@/object-record/read-only/utils/isObjectMetadataReadOnly'; const StyledDiv = styled.div` display: flex; @@ -21,7 +22,9 @@ type ObjectFieldsProps = { }; export const ObjectFields = ({ objectMetadataItem }: ObjectFieldsProps) => { - const shouldDisplayAddFieldButton = !objectMetadataItem.isRemote; + const readonly = isObjectMetadataReadOnly({ + objectMetadataItem, + }); const { t } = useLingui(); const objectLabelSingular = objectMetadataItem.labelSingular; @@ -36,7 +39,7 @@ export const ObjectFields = ({ objectMetadataItem }: ObjectFieldsProps) => { objectMetadataItem={objectMetadataItem} mode="view" /> - {shouldDisplayAddFieldButton && ( + {!readonly && ( { const { t } = useLingui(); + const readonly = isObjectMetadataReadOnly({ objectMetadataItem }); const navigate = useNavigateSettings(); const { updateOneObjectMetadataItem } = useUpdateOneObjectMetadataItem(); const handleDisable = async () => { @@ -59,17 +61,22 @@ export const ObjectSettings = ({ objectMetadataItem }: ObjectSettingsProps) => { /> - -
- -
-
+ {!readonly && ( + +
+ +
+
+ )} ); }; diff --git a/packages/twenty-front/src/modules/settings/data-model/objects/components/SettingsDataModelObjectSummary.tsx b/packages/twenty-front/src/modules/settings/data-model/objects/components/SettingsDataModelObjectSummary.tsx index f80d84b49e..ebef9e29ba 100644 --- a/packages/twenty-front/src/modules/settings/data-model/objects/components/SettingsDataModelObjectSummary.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/objects/components/SettingsDataModelObjectSummary.tsx @@ -2,13 +2,12 @@ import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { SettingsDataModelObjectTypeTag } from '@/settings/data-model/objects/components/SettingsDataModelObjectTypeTag'; -import { getObjectTypeLabel } from '@/settings/data-model/utils/getObjectTypeLabel'; import { IconBox, OverflowingTextWithTooltip, useIcons, } from 'twenty-ui/display'; +import { SettingsItemTypeTag } from '@/settings/components/SettingsItemTypeTag'; export type SettingsDataModelObjectPreviewProps = { className?: string; @@ -69,7 +68,6 @@ const SettingsDataModelObjectPreviewItem = ({ const theme = useTheme(); const { getIcon } = useIcons(); const ObjectIcon = getIcon(objectMetadataItem.icon); - const objectTypeLabel = getObjectTypeLabel(objectMetadataItem); return ( <> @@ -90,7 +88,7 @@ const SettingsDataModelObjectPreviewItem = ({ } /> - + ); diff --git a/packages/twenty-front/src/modules/settings/data-model/objects/components/SettingsDataModelObjectTypeTag.tsx b/packages/twenty-front/src/modules/settings/data-model/objects/components/SettingsDataModelObjectTypeTag.tsx deleted file mode 100644 index dc284dc918..0000000000 --- a/packages/twenty-front/src/modules/settings/data-model/objects/components/SettingsDataModelObjectTypeTag.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { type ObjectTypeLabel } from '@/settings/data-model/utils/getObjectTypeLabel'; -import { Tag } from 'twenty-ui/components'; - -type SettingsDataModelObjectTypeTagProps = { - objectTypeLabel: ObjectTypeLabel; - className?: string; -}; - -export const SettingsDataModelObjectTypeTag = ({ - className, - objectTypeLabel, -}: SettingsDataModelObjectTypeTagProps) => { - return ( - - ); -}; diff --git a/packages/twenty-front/src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm.tsx b/packages/twenty-front/src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm.tsx index e28c5be4c0..1a6dddbefb 100644 --- a/packages/twenty-front/src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm.tsx @@ -139,7 +139,11 @@ export const SettingsDataModelObjectAboutForm = ({ render={({ field: { onChange, value } }) => ( { + if (disableEdition) { + return; + } onChange(iconKey); onNewDirtyField?.(); }} @@ -170,9 +174,10 @@ export const SettingsDataModelObjectAboutForm = ({ }} onBlur={() => onNewDirtyField?.()} disabled={ - objectMetadataItem && - !objectMetadataItem?.isCustom && - isLabelSyncedWithName + disableEdition || + (objectMetadataItem && + !objectMetadataItem?.isCustom && + isLabelSyncedWithName) } fullWidth maxLength={OBJECT_NAME_MAXIMUM_LENGTH} @@ -201,9 +206,10 @@ export const SettingsDataModelObjectAboutForm = ({ }} onBlur={() => onNewDirtyField?.()} disabled={ - objectMetadataItem && - !objectMetadataItem?.isCustom && - isLabelSyncedWithName + disableEdition || + (objectMetadataItem && + !objectMetadataItem?.isCustom && + isLabelSyncedWithName) } fullWidth maxLength={OBJECT_NAME_MAXIMUM_LENGTH} @@ -222,6 +228,7 @@ export const SettingsDataModelObjectAboutForm = ({ value={value ?? undefined} onChange={(nextValue) => onChange(nextValue ?? null)} onBlur={() => onNewDirtyField?.()} + disabled={disableEdition} /> )} /> @@ -326,6 +333,7 @@ export const SettingsDataModelObjectAboutForm = ({ description={t`Should changing an object's label also change the API?`} checked={value ?? true} advancedMode + disabled={disableEdition} onChange={(value) => { onChange(value); const isCustomObject = diff --git a/packages/twenty-front/src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectIdentifiersForm.tsx b/packages/twenty-front/src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectIdentifiersForm.tsx index c5e0d2fea8..70e28fea6f 100644 --- a/packages/twenty-front/src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectIdentifiersForm.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectIdentifiersForm.tsx @@ -16,6 +16,7 @@ import { useNavigate } from 'react-router-dom'; import { isLabelIdentifierFieldMetadataTypes } from 'twenty-shared/utils'; import { IconCircleOff, IconPlus, useIcons } from 'twenty-ui/display'; import { type SelectOption } from 'twenty-ui/input'; +import { isObjectMetadataReadOnly } from '@/object-record/read-only/utils/isObjectMetadataReadOnly'; export const settingsDataModelObjectIdentifiersFormSchema = objectMetadataItemSchema.pick({ @@ -44,6 +45,7 @@ const StyledContainer = styled.div` export const SettingsDataModelObjectIdentifiersForm = ({ objectMetadataItem, }: SettingsDataModelObjectIdentifiersFormProps) => { + const readonly = isObjectMetadataReadOnly({ objectMetadataItem }); const formConfig = useForm({ mode: 'onTouched', resolver: zodResolver(settingsDataModelObjectIdentifiersFormSchema), @@ -134,7 +136,7 @@ export const SettingsDataModelObjectIdentifiersForm = ({ options={options} value={value} withSearchInput={label === t`Record label`} - disabled={!objectMetadataItem.isCustom} + disabled={!objectMetadataItem.isCustom || readonly} callToActionButton={ label === t`Record label` ? { diff --git a/packages/twenty-front/src/modules/settings/data-model/utils/getItemTagInfo.ts b/packages/twenty-front/src/modules/settings/data-model/utils/getItemTagInfo.ts new file mode 100644 index 0000000000..36c6cb6ce4 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/data-model/utils/getItemTagInfo.ts @@ -0,0 +1,51 @@ +import { isDefined } from 'twenty-shared/utils'; + +export type ItemTagInfo = + | StandardItemTagInfo + | CustomItemTagInfo + | RemoteItemTagInfo + | ManagedItemTagInfo; + +type StandardItemTagInfo = { + labelText: 'Standard'; + labelColor: 'blue'; +}; + +type CustomItemTagInfo = { + labelText: 'Custom'; + labelColor: 'orange'; +}; + +type RemoteItemTagInfo = { + labelText: 'Remote'; + labelColor: 'green'; +}; + +type ManagedItemTagInfo = { + labelText: 'Managed'; + labelColor: 'sky'; +}; + +export const getItemTagInfo = ({ + isCustom, + isRemote, + applicationId, +}: { + isCustom?: boolean; + isRemote?: boolean; + applicationId?: string | null; +}): ItemTagInfo => { + if (isDefined(applicationId)) { + return { labelText: 'Managed', labelColor: 'sky' }; + } + + if (isCustom!!) { + return { labelText: 'Custom', labelColor: 'orange' }; + } + + if (isRemote!!) { + return { labelText: 'Remote', labelColor: 'green' }; + } + + return { labelText: 'Standard', labelColor: 'blue' }; +}; diff --git a/packages/twenty-front/src/modules/settings/data-model/utils/getObjectTypeLabel.ts b/packages/twenty-front/src/modules/settings/data-model/utils/getObjectTypeLabel.ts deleted file mode 100644 index 242c719757..0000000000 --- a/packages/twenty-front/src/modules/settings/data-model/utils/getObjectTypeLabel.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; - -export type ObjectTypeLabel = - | StandardObjectTypeLabel - | CustomObjectTypeLabel - | RemoteObjectTypeLabel; - -type StandardObjectTypeLabel = { - labelText: 'Standard'; - labelColor: 'blue'; -}; - -type CustomObjectTypeLabel = { - labelText: 'Custom'; - labelColor: 'orange'; -}; - -type RemoteObjectTypeLabel = { - labelText: 'Remote'; - labelColor: 'green'; -}; - -export const getObjectTypeLabel = ( - objectMetadataItem: Pick, -): ObjectTypeLabel => - objectMetadataItem.isCustom - ? { - labelText: 'Custom', - labelColor: 'orange', - } - : objectMetadataItem.isRemote - ? { - labelText: 'Remote', - labelColor: 'green', - } - : { - labelText: 'Standard', - labelColor: 'blue', - }; diff --git a/packages/twenty-front/src/modules/settings/developers/components/SettingsDevelopersWebhookForm.tsx b/packages/twenty-front/src/modules/settings/developers/components/SettingsDevelopersWebhookForm.tsx index ddb49b160e..6f298df4be 100644 --- a/packages/twenty-front/src/modules/settings/developers/components/SettingsDevelopersWebhookForm.tsx +++ b/packages/twenty-front/src/modules/settings/developers/components/SettingsDevelopersWebhookForm.tsx @@ -1,19 +1,15 @@ import { Controller, FormProvider } from 'react-hook-form'; -import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsSkeletonLoader } from '@/settings/components/SettingsSkeletonLoader'; import { type WebhookFormMode } from '@/settings/developers/constants/WebhookFormMode'; import { useWebhookForm } from '@/settings/developers/hooks/useWebhookForm'; -import { Select } from '@/ui/input/components/Select'; import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput'; import { TextArea } from '@/ui/input/components/TextArea'; import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; import { useModal } from '@/ui/layout/modal/hooks/useModal'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; -import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; -import styled from '@emotion/styled'; import { Trans, useLingui } from '@lingui/react/macro'; import { SettingsPath } from 'twenty-shared/types'; import { @@ -22,38 +18,11 @@ import { isDefined, isValidUrl, } from 'twenty-shared/utils'; -import { - H2Title, - IconBox, - IconNorthStar, - IconPlus, - IconTrash, - useIcons, -} from 'twenty-ui/display'; -import { Button, IconButton, type SelectOption } from 'twenty-ui/input'; +import { H2Title, IconTrash } from 'twenty-ui/display'; +import { Button } from 'twenty-ui/input'; import { Section } from 'twenty-ui/layout'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; - -const OBJECT_DROPDOWN_WIDTH = 340; -const ACTION_DROPDOWN_WIDTH = 140; -const OBJECT_MOBILE_WIDTH = 150; -const ACTION_MOBILE_WIDTH = 140; - -const StyledFilterRow = styled.div<{ isMobile: boolean }>` - display: grid; - grid-template-columns: ${({ isMobile }) => - isMobile - ? `${OBJECT_MOBILE_WIDTH}px ${ACTION_MOBILE_WIDTH}px auto` - : `${OBJECT_DROPDOWN_WIDTH}px ${ACTION_DROPDOWN_WIDTH}px auto`}; - gap: ${({ theme }) => theme.spacing(2)}; - margin-bottom: ${({ theme }) => theme.spacing(2)}; - align-items: center; -`; - -const StyledPlaceholder = styled.div` - height: ${({ theme }) => theme.spacing(8)}; - width: ${({ theme }) => theme.spacing(8)}; -`; +import { SettingsDatabaseEventsForm } from '@/settings/components/SettingsDatabaseEventsForm'; const DELETE_WEBHOOK_MODAL_ID = 'delete-webhook-modal'; @@ -68,9 +37,6 @@ export const SettingsDevelopersWebhookForm = ({ }: SettingsDevelopersWebhookFormProps) => { const { t } = useLingui(); const navigate = useNavigateSettings(); - const { objectMetadataItems } = useObjectMetadataItems(); - const isMobile = useIsMobile(); - const { getIcon } = useIcons(); const { openModal } = useModal(); const { formConfig, @@ -99,22 +65,6 @@ export const SettingsDevelopersWebhookForm = ({ return ; } - const objectOptions: SelectOption[] = [ - { label: 'All Objects', value: '*', Icon: IconNorthStar }, - ...objectMetadataItems.map((item) => ({ - label: item.labelPlural, - value: item.nameSingular, - Icon: getIcon(item.icon), - })), - ]; - - const actionOptions: SelectOption[] = [ - { label: 'All', value: '*', Icon: IconNorthStar }, - { label: 'Created', value: 'created', Icon: IconPlus }, - { label: 'Updated', value: 'updated', Icon: IconBox }, - { label: 'Deleted', value: 'deleted', Icon: IconTrash }, - ]; - const descriptionTextAreaId = `${webhookId}-description`; const targetUrlTextInputId = `${webhookId}-target-url`; const secretTextInputId = `${webhookId}-secret`; @@ -200,41 +150,11 @@ export const SettingsDevelopersWebhookForm = ({ name="operations" control={formConfig.control} render={({ field: { value } }) => ( - <> - {value.map((operation, index) => ( - - - updateOperation(index, 'action', newValue) - } - fullWidth - /> - {isDefined(operation.object) ? ( - removeOperation(index)} - /> - ) : ( - - )} - - ))} - + )} /> diff --git a/packages/twenty-front/src/modules/settings/hooks/useSettingsNavigationItems.tsx b/packages/twenty-front/src/modules/settings/hooks/useSettingsNavigationItems.tsx index 47e8bc91c8..ab36660e0e 100644 --- a/packages/twenty-front/src/modules/settings/hooks/useSettingsNavigationItems.tsx +++ b/packages/twenty-front/src/modules/settings/hooks/useSettingsNavigationItems.tsx @@ -17,7 +17,6 @@ import { type IconComponent, IconCurrencyDollar, IconDoorEnter, - IconFunction, IconHierarchy2, IconKey, IconLock, @@ -26,6 +25,7 @@ import { IconServer, IconSettings, IconSparkles, + IconPuzzle2, IconUserCircle, IconUsers, IconWorld, @@ -56,13 +56,15 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => { const billing = useRecoilValue(billingState); const { signOut } = useAuth(); - const isFunctionSettingsEnabled = false; const isBillingEnabled = billing?.isBillingEnabled ?? false; const currentUser = useRecoilValue(currentUserState); const isAdminEnabled = (currentUser?.canImpersonate || currentUser?.canAccessFullAdminPanel) ?? false; const isAIEnabled = useIsFeatureEnabled(FeatureFlagKey.IS_AI_ENABLED); + const isApplicationEnabled = useIsFeatureEnabled( + FeatureFlagKey.IS_APPLICATION_ENABLED, + ); const permissionMap = usePermissionFlagMap(); return [ @@ -152,6 +154,15 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => { Icon: IconApps, isHidden: !permissionMap[PermissionFlagType.API_KEYS_AND_WEBHOOKS], }, + { + label: t`Applications`, + path: SettingsPath.Applications, + Icon: IconPuzzle2, + isHidden: + !isApplicationEnabled || + !permissionMap[PermissionFlagType.WORKSPACE], + isNew: true, + }, { label: t`AI`, path: SettingsPath.AI, @@ -160,13 +171,6 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => { !isAIEnabled || !permissionMap[PermissionFlagType.WORKSPACE], isNew: true, }, - { - label: t`Functions`, - path: SettingsPath.ServerlessFunctions, - Icon: IconFunction, - isHidden: !isFunctionSettingsEnabled, - isAdvanced: true, - }, { label: t`Security`, path: SettingsPath.Security, diff --git a/packages/twenty-front/src/modules/settings/serverless-functions/components/SettingsServerlessFunctionCodeEditor.tsx b/packages/twenty-front/src/modules/settings/serverless-functions/components/SettingsServerlessFunctionCodeEditor.tsx index fe3c5c75fa..07e253dd5c 100644 --- a/packages/twenty-front/src/modules/settings/serverless-functions/components/SettingsServerlessFunctionCodeEditor.tsx +++ b/packages/twenty-front/src/modules/settings/serverless-functions/components/SettingsServerlessFunctionCodeEditor.tsx @@ -1,7 +1,7 @@ import { useGetAvailablePackages } from '@/settings/serverless-functions/hooks/useGetAvailablePackages'; import { type EditorProps, type Monaco } from '@monaco-editor/react'; import dotenv from 'dotenv'; -import { type editor, MarkerSeverity } from 'monaco-editor'; +import { type editor } from 'monaco-editor'; import { AutoTypings } from 'monaco-editor-auto-typings'; import { useParams } from 'react-router-dom'; import { isDefined } from 'twenty-shared/utils'; @@ -20,14 +20,12 @@ type SettingsServerlessFunctionCodeEditorProps = Omit< currentFilePath: string; files: File[]; onChange: (value: string) => void; - setIsCodeValid: (isCodeValid: boolean) => void; }; export const SettingsServerlessFunctionCodeEditor = ({ currentFilePath, files, onChange, - setIsCodeValid, height = 450, options = undefined, }: SettingsServerlessFunctionCodeEditorProps) => { @@ -106,16 +104,6 @@ export const SettingsServerlessFunctionCodeEditor = ({ } }; - const handleEditorValidation = (markers: editor.IMarker[]) => { - for (const marker of markers) { - if (marker.severity === MarkerSeverity.Error) { - setIsCodeValid?.(false); - return; - } - } - setIsCodeValid?.(true); - }; - return ( isDefined(currentFile) && isDefined(availablePackages) && ( @@ -125,7 +113,6 @@ export const SettingsServerlessFunctionCodeEditor = ({ language={currentFile.language} onMount={handleEditorDidMount} onChange={onChange} - onValidate={handleEditorValidation} options={options} variant="with-header" /> diff --git a/packages/twenty-front/src/modules/settings/serverless-functions/components/SettingsServerlessFunctionNewForm.tsx b/packages/twenty-front/src/modules/settings/serverless-functions/components/SettingsServerlessFunctionNewForm.tsx index 416baa0e32..690d2f16b8 100644 --- a/packages/twenty-front/src/modules/settings/serverless-functions/components/SettingsServerlessFunctionNewForm.tsx +++ b/packages/twenty-front/src/modules/settings/serverless-functions/components/SettingsServerlessFunctionNewForm.tsx @@ -14,9 +14,11 @@ const StyledInputsContainer = styled.div` export const SettingsServerlessFunctionNewForm = ({ formValues, onChange, + readonly = false, }: { formValues: ServerlessFunctionNewFormValues; onChange: (key: string) => (value: string) => void; + readonly?: boolean; }) => { const descriptionTextAreaId = `${formValues.name}-description`; const nameTextInputId = `${formValues.name}-name`; @@ -32,6 +34,7 @@ export const SettingsServerlessFunctionNewForm = ({ autoFocusOnMount value={formValues.name} onChange={onChange('name')} + readOnly={readonly} />