diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql index 04afa80ec4..88af092142 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql +++ b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql @@ -164,6 +164,7 @@ enum PermissionFlagType { UPLOAD_FILE DOWNLOAD_FILE SEND_EMAIL_TOOL + CREATE_CALENDAR_EVENT_TOOL HTTP_REQUEST_TOOL CODE_INTERPRETER_TOOL IMPORT_CSV @@ -2704,6 +2705,13 @@ type ChannelSyncSuccess { success: Boolean! } +type CreateCalendarEventOutput { + success: Boolean! + iCalUid: String + conferenceLink: String + error: String +} + type BarChartSeries { key: String! label: String! @@ -3088,17 +3096,9 @@ type Query { commandMenuItem(id: UUID!): CommandMenuItem frontComponents: [FrontComponent!]! frontComponent(id: UUID!): FrontComponent - previewMessageCampaignAudience(input: PreviewMessageCampaignAudienceInput!): CampaignAudiencePreviewDTO! - unsubscribeTopics: [UnsubscribeTopic!]! - unsubscribePagePreviewUrl: String! - myMessageChannels(connectedAccountId: UUID): [MessageChannel!]! - getEmailingDomains: [EmailingDomain!]! - myConnectedAccounts: [ConnectedAccountPublicDTO!]! - getRoles: [Role!]! - getToolIndex: [ToolIndexEntry!]! - getToolInputSchema(toolName: String!): JSON - webhooks: [Webhook!]! - webhook(id: UUID!): Webhook + currentWorkspace: Workspace! + getPublicWorkspaceDataByDomain(origin: String): PublicWorkspaceData! + getPublicWorkspaceDataById(id: UUID!): PublicWorkspaceDataSummary! field( """The id of the record to find.""" id: UUID! @@ -3112,6 +3112,26 @@ type Query { ): FieldConnection! getViewGroups(viewId: String): [ViewGroup!]! getViewGroup(id: String!): ViewGroup + findManyApplications: [Application!]! + findOneApplication(id: UUID, universalIdentifier: UUID): Application! + getRoles: [Role!]! + findApplicationRegistrationByClientId(clientId: String!): PublicApplicationRegistration + findApplicationRegistrationByUniversalIdentifier(universalIdentifier: String!): ApplicationRegistration + findManyApplicationRegistrations: [ApplicationRegistration!]! + findOneApplicationRegistration(id: String!): ApplicationRegistration! + findApplicationRegistrationStats(id: String!): ApplicationRegistrationStats! + findApplicationRegistrationVariables(applicationRegistrationId: String!): [ApplicationRegistrationVariableDTO!]! + applicationRegistrationTarballUrl(id: String!): String + previewMessageCampaignAudience(input: PreviewMessageCampaignAudienceInput!): CampaignAudiencePreviewDTO! + unsubscribeTopics: [UnsubscribeTopic!]! + unsubscribePagePreviewUrl: String! + myMessageChannels(connectedAccountId: UUID): [MessageChannel!]! + getEmailingDomains: [EmailingDomain!]! + myConnectedAccounts: [ConnectedAccountPublicDTO!]! + getToolIndex: [ToolIndexEntry!]! + getToolInputSchema(toolName: String!): JSON + webhooks: [Webhook!]! + webhook(id: UUID!): Webhook myMessageFolders(messageChannelId: UUID): [MessageFolder!]! myCalendarChannels(connectedAccountId: UUID): [CalendarChannel!]! minimalMetadata: MinimalMetadata! @@ -3132,19 +3152,7 @@ type Query { checkWorkspaceSubdomainAvailability(subdomain: String!): SubdomainAvailabilityDTO! getWorkspaceCreationDefaults: WorkspaceCreationDefaultsDTO! validatePasswordResetToken(passwordResetToken: String!): ValidatePasswordResetToken! - findApplicationRegistrationByClientId(clientId: String!): PublicApplicationRegistration - findApplicationRegistrationByUniversalIdentifier(universalIdentifier: String!): ApplicationRegistration - findManyApplicationRegistrations: [ApplicationRegistration!]! - findOneApplicationRegistration(id: String!): ApplicationRegistration! - findApplicationRegistrationStats(id: String!): ApplicationRegistrationStats! - findApplicationRegistrationVariables(applicationRegistrationId: String!): [ApplicationRegistrationVariableDTO!]! - applicationRegistrationTarballUrl(id: String!): String currentUser: User! - currentWorkspace: Workspace! - getPublicWorkspaceDataByDomain(origin: String): PublicWorkspaceData! - getPublicWorkspaceDataById(id: UUID!): PublicWorkspaceDataSummary! - findManyApplications: [Application!]! - findOneApplication(id: UUID, universalIdentifier: UUID): Application! getSSOIdentityProviders: [FindAvailableSSOIDP!]! eventLogs(input: EventLogQueryInput!): EventLogQueryResult! pieChartData(input: PieChartDataInput!): PieChartData! @@ -3344,6 +3352,40 @@ type Mutation { createFrontComponent(input: CreateFrontComponentInput!): FrontComponent! updateFrontComponent(input: UpdateFrontComponentInput!): FrontComponent! deleteFrontComponent(id: UUID!): FrontComponent! + activateWorkspace(data: ActivateWorkspaceInput!): Workspace! + updateWorkspace(data: UpdateWorkspaceInput!): Workspace! + deleteCurrentWorkspace: Workspace! + checkCustomDomainValidRecords: DomainValidRecords + createOneField(input: CreateOneFieldMetadataInput!): Field! + updateOneField(input: UpdateOneFieldMetadataInput!): Field! + deleteOneField(input: DeleteOneFieldInput!): Field! + createViewGroup(input: CreateViewGroupInput!): ViewGroup! + createManyViewGroups(inputs: [CreateViewGroupInput!]!): [ViewGroup!]! + updateViewGroup(input: UpdateViewGroupInput!): ViewGroup! + updateManyViewGroups(inputs: [UpdateViewGroupInput!]!): [ViewGroup!]! + deleteViewGroup(input: DeleteViewGroupInput!): ViewGroup! + destroyViewGroup(input: DestroyViewGroupInput!): ViewGroup! + runWorkspaceMigration(workspaceMigration: WorkspaceMigrationInput!): Boolean! + uninstallApplication(universalIdentifier: String!): Boolean! + updateWorkspaceMemberRole(workspaceMemberId: UUID!, roleId: UUID!): WorkspaceMember! + createOneRole(createRoleInput: CreateRoleInput!): Role! + updateOneRole(updateRoleInput: UpdateRoleInput!): Role! + deleteOneRole(roleId: UUID!): String! + upsertObjectPermissions(upsertObjectPermissionsInput: UpsertObjectPermissionsInput!): [ObjectPermission!]! + upsertPermissionFlags(upsertPermissionFlagsInput: UpsertPermissionFlagsInput!): [RolePermissionFlag!]! + upsertFieldPermissions(upsertFieldPermissionsInput: UpsertFieldPermissionsInput!): [FieldPermission!]! + upsertRowLevelPermissionPredicates(input: UpsertRowLevelPermissionPredicatesInput!): UpsertRowLevelPermissionPredicatesResult! + assignRoleToAgent(agentId: UUID!, roleId: UUID!): Boolean! + removeRoleFromAgent(agentId: UUID!): Boolean! + createApplicationRegistration(input: CreateApplicationRegistrationInput!): CreateApplicationRegistration! + updateApplicationRegistration(input: UpdateApplicationRegistrationInput!): ApplicationRegistration! + deleteApplicationRegistration(id: String!): Boolean! + rotateApplicationRegistrationClientSecret(id: String!): RotateClientSecret! + createApplicationRegistrationVariable(input: CreateApplicationRegistrationVariableInput!): ApplicationRegistrationVariable! + updateApplicationRegistrationVariable(input: UpdateApplicationRegistrationVariableInput!): ApplicationRegistrationVariable! + deleteApplicationRegistrationVariable(id: String!): Boolean! + uploadAppTarball(file: Upload!, universalIdentifier: String): ApplicationRegistration! + transferApplicationRegistrationOwnership(applicationRegistrationId: String!, targetWorkspaceSubdomain: String!): ApplicationRegistration! sendEmailViaEmailingDomain(input: SendEmailViaDomainInput!): SendEmailViaDomainOutput! sendMessageCampaign(input: SendMessageCampaignInput!): SendMessageCampaignOutputDTO! createUnsubscribeTopic(input: CreateUnsubscribeTopicInput!): UnsubscribeTopic! @@ -3356,29 +3398,10 @@ type Mutation { deleteEmailingDomain(id: String!): Boolean! verifyEmailingDomain(id: String!): EmailingDomain! deleteConnectedAccount(id: UUID!): ConnectedAccountPublicDTO! - updateWorkspaceMemberRole(workspaceMemberId: UUID!, roleId: UUID!): WorkspaceMember! - createOneRole(createRoleInput: CreateRoleInput!): Role! - updateOneRole(updateRoleInput: UpdateRoleInput!): Role! - deleteOneRole(roleId: UUID!): String! - upsertObjectPermissions(upsertObjectPermissionsInput: UpsertObjectPermissionsInput!): [ObjectPermission!]! - upsertPermissionFlags(upsertPermissionFlagsInput: UpsertPermissionFlagsInput!): [RolePermissionFlag!]! - upsertFieldPermissions(upsertFieldPermissionsInput: UpsertFieldPermissionsInput!): [FieldPermission!]! - upsertRowLevelPermissionPredicates(input: UpsertRowLevelPermissionPredicatesInput!): UpsertRowLevelPermissionPredicatesResult! - assignRoleToAgent(agentId: UUID!, roleId: UUID!): Boolean! - removeRoleFromAgent(agentId: UUID!): Boolean! runAgent(input: RunAgentInput!): RunAgentResult! createWebhook(input: CreateWebhookInput!): Webhook! updateWebhook(input: UpdateWebhookInput!): Webhook! deleteWebhook(id: UUID!): Webhook! - createOneField(input: CreateOneFieldMetadataInput!): Field! - updateOneField(input: UpdateOneFieldMetadataInput!): Field! - deleteOneField(input: DeleteOneFieldInput!): Field! - createViewGroup(input: CreateViewGroupInput!): ViewGroup! - createManyViewGroups(inputs: [CreateViewGroupInput!]!): [ViewGroup!]! - updateViewGroup(input: UpdateViewGroupInput!): ViewGroup! - updateManyViewGroups(inputs: [UpdateViewGroupInput!]!): [ViewGroup!]! - deleteViewGroup(input: DeleteViewGroupInput!): ViewGroup! - destroyViewGroup(input: DestroyViewGroupInput!): ViewGroup! updateMessageFolder(input: UpdateMessageFolderInput!): MessageFolder! updateMessageFolders(input: UpdateMessageFoldersInput!): [MessageFolder!]! updateCalendarChannel(input: UpdateCalendarChannelInput!): CalendarChannel! @@ -3415,15 +3438,6 @@ type Mutation { generatePlaygroundToken: AuthToken! emailPasswordResetLink(email: String!, workspaceId: UUID): EmailPasswordResetLink! updatePasswordViaResetToken(passwordResetToken: String!, newPassword: String!): InvalidatePassword! - createApplicationRegistration(input: CreateApplicationRegistrationInput!): CreateApplicationRegistration! - updateApplicationRegistration(input: UpdateApplicationRegistrationInput!): ApplicationRegistration! - deleteApplicationRegistration(id: String!): Boolean! - rotateApplicationRegistrationClientSecret(id: String!): RotateClientSecret! - createApplicationRegistrationVariable(input: CreateApplicationRegistrationVariableInput!): ApplicationRegistrationVariable! - updateApplicationRegistrationVariable(input: UpdateApplicationRegistrationVariableInput!): ApplicationRegistrationVariable! - deleteApplicationRegistrationVariable(id: String!): Boolean! - uploadAppTarball(file: Upload!, universalIdentifier: String): ApplicationRegistration! - transferApplicationRegistrationOwnership(applicationRegistrationId: String!, targetWorkspaceSubdomain: String!): ApplicationRegistration! initiateOTPProvisioning(loginToken: String!, origin: String!): InitiateTwoFactorAuthenticationProvisioning! initiateOTPProvisioningForAuthenticatedUser: InitiateTwoFactorAuthenticationProvisioning! deleteTwoFactorAuthenticationMethod(twoFactorAuthenticationMethodId: UUID!): DeleteTwoFactorAuthenticationMethod! @@ -3433,12 +3447,6 @@ type Mutation { updateWorkspaceMemberSettings(input: UpdateWorkspaceMemberSettingsInput!): Boolean! updateUserEmail(newEmail: String!, verifyEmailRedirectPath: String): Boolean! resendEmailVerificationToken(email: String!, origin: String!): ResendEmailVerificationToken! - activateWorkspace(data: ActivateWorkspaceInput!): Workspace! - updateWorkspace(data: UpdateWorkspaceInput!): Workspace! - deleteCurrentWorkspace: Workspace! - checkCustomDomainValidRecords: DomainValidRecords - runWorkspaceMigration(workspaceMigration: WorkspaceMigrationInput!): Boolean! - uninstallApplication(universalIdentifier: String!): Boolean! createOIDCIdentityProvider(input: SetupOIDCSsoInput!): SetupSso! createSAMLIdentityProvider(input: SetupSAMLSsoInput!): SetupSso! deleteSSOIdentityProvider(input: DeleteSsoInput!): DeleteSso! @@ -3447,6 +3455,7 @@ type Mutation { trackAnalytics(type: AnalyticsType!, name: String, event: String, properties: JSON): Analytics! duplicateDashboard(id: UUID!): DuplicatedDashboard! impersonate(userId: UUID!, workspaceId: UUID!): Impersonate! + createCalendarEvent(input: CreateCalendarEventInput!): CreateCalendarEventOutput! sendEmail(input: SendEmailInput!): SendEmailOutput! startChannelSync(connectedAccountId: UUID!): ChannelSyncSuccess! saveImapSmtpCaldavAccount(handle: String!, connectionParameters: EmailAccountConnectionParameters!, id: UUID): ImapSmtpCaldavConnectionSuccess! @@ -4112,60 +4121,147 @@ input UpdateFrontComponentInputUpdates { description: String } -input SendEmailViaDomainInput { - emailingDomainId: String! - to: [String!]! - cc: [String!] - bcc: [String!] - subject: String! - text: String! - html: String - from: String! - replyTo: [String!] +input ActivateWorkspaceInput { + """ + Deprecated: the workspace name is set at creation (signUpInNewWorkspace) and this field is ignored during activation. Kept for backward compatibility. + """ + displayName: String } -input SendMessageCampaignInput { - listId: String! - unsubscribeTopicId: String - subject: String! - body: String! - fromAddress: String! +input UpdateWorkspaceInput { + subdomain: String + customDomain: String + displayName: String + logo: String + inviteHash: String + isPublicInviteLinkEnabled: Boolean + allowImpersonation: Boolean + isGoogleAuthEnabled: Boolean + isMicrosoftAuthEnabled: Boolean + isPasswordAuthEnabled: Boolean + isGoogleAuthBypassEnabled: Boolean + isMicrosoftAuthBypassEnabled: Boolean + isPasswordAuthBypassEnabled: Boolean + defaultRoleId: UUID + isTwoFactorAuthenticationEnforced: Boolean + trashRetentionDays: Float + eventLogRetentionDays: Float + fastModel: String + smartModel: String + aiAdditionalInstructions: String + editableProfileFields: [String!] + enabledAiModelIds: [String!] + useRecommendedModels: Boolean + isInternalMessagesImportEnabled: Boolean } -input CreateUnsubscribeTopicInput { +input CreateOneFieldMetadataInput { + """The record to create""" + field: CreateFieldInput! +} + +input CreateFieldInput { + type: FieldMetadataType! name: String! + label: String! description: String - visibility: UnsubscribeTopicVisibility + icon: String + isActive: Boolean + isSystem: Boolean + isUIEditable: Boolean + isUIReadOnly: Boolean + isNullable: Boolean + isUnique: Boolean + defaultValue: JSON + options: JSON + settings: JSON + objectMetadataId: UUID! + isLabelSyncedWithName: Boolean + isRemoteCreation: Boolean + relationCreationPayload: JSON + morphRelationsCreationPayload: [JSON!] } -input UpdateUnsubscribeTopicInput { - id: String! - name: String - description: String - visibility: UnsubscribeTopicVisibility -} - -input UpdateMessageChannelInput { +input UpdateOneFieldMetadataInput { + """The id of the record to update""" id: UUID! - update: UpdateMessageChannelInputUpdates! + + """The record to update""" + update: UpdateFieldInput! } -input UpdateMessageChannelInputUpdates { - visibility: MessageChannelVisibility - isContactAutoCreationEnabled: Boolean - contactAutoCreationPolicy: MessageChannelContactAutoCreationPolicy - messageFolderImportPolicy: MessageFolderImportPolicy - isSyncEnabled: Boolean - excludeNonProfessionalEmails: Boolean - excludeGroupEmails: Boolean +input UpdateFieldInput { + universalIdentifier: String + name: String + label: String + description: String + icon: String + isActive: Boolean + isSystem: Boolean + isUIEditable: Boolean + isUIReadOnly: Boolean + isNullable: Boolean + isUnique: Boolean + defaultValue: JSON + options: JSON + settings: JSON + objectMetadataId: UUID + isLabelSyncedWithName: Boolean + morphRelationsUpdatePayload: [JSON!] } -input CreateEmailGroupChannelInput { - handle: String! +input DeleteOneFieldInput { + """The id of the field to delete.""" + id: UUID! } -input CreateEmailingDomainInput { - domain: String! +input CreateViewGroupInput { + id: UUID + isVisible: Boolean = true + fieldValue: String! + position: Float = 0 + viewId: UUID! +} + +input UpdateViewGroupInput { + """The id of the view group to update""" + id: UUID! + + """The view group to update""" + update: UpdateViewGroupInputUpdates! +} + +input UpdateViewGroupInputUpdates { + fieldMetadataId: UUID + isVisible: Boolean + fieldValue: String + position: Float +} + +input DeleteViewGroupInput { + """The id of the view group to delete.""" + id: UUID! +} + +input DestroyViewGroupInput { + """The id of the view group to destroy.""" + id: UUID! +} + +input WorkspaceMigrationInput { + actions: [WorkspaceMigrationDeleteActionInput!]! +} + +input WorkspaceMigrationDeleteActionInput { + type: WorkspaceMigrationActionType! + metadataName: AllMetadataName! + universalIdentifier: String! +} + +enum WorkspaceMigrationActionType { + delete + create + update } input CreateRoleInput { @@ -4263,6 +4359,101 @@ input RowLevelPermissionPredicateGroupInput { positionInRowLevelPermissionPredicateGroup: Float } +input CreateApplicationRegistrationInput { + name: String! + universalIdentifier: String + oAuthRedirectUris: [String!] + oAuthScopes: [String!] +} + +input UpdateApplicationRegistrationInput { + id: String! + update: UpdateApplicationRegistrationPayload! +} + +input UpdateApplicationRegistrationPayload { + name: String + oAuthRedirectUris: [String!] + oAuthScopes: [String!] + isListed: Boolean + isPreInstalled: Boolean +} + +input CreateApplicationRegistrationVariableInput { + applicationRegistrationId: String! + key: String! + value: String! + description: String + isSecret: Boolean +} + +input UpdateApplicationRegistrationVariableInput { + id: String! + update: UpdateApplicationRegistrationVariablePayload! +} + +input UpdateApplicationRegistrationVariablePayload { + value: String + resetValue: Boolean + description: String +} + +input SendEmailViaDomainInput { + emailingDomainId: String! + to: [String!]! + cc: [String!] + bcc: [String!] + subject: String! + text: String! + html: String + from: String! + replyTo: [String!] +} + +input SendMessageCampaignInput { + listId: String! + unsubscribeTopicId: String + subject: String! + body: String! + fromAddress: String! +} + +input CreateUnsubscribeTopicInput { + name: String! + description: String + visibility: UnsubscribeTopicVisibility +} + +input UpdateUnsubscribeTopicInput { + id: String! + name: String + description: String + visibility: UnsubscribeTopicVisibility +} + +input UpdateMessageChannelInput { + id: UUID! + update: UpdateMessageChannelInputUpdates! +} + +input UpdateMessageChannelInputUpdates { + visibility: MessageChannelVisibility + isContactAutoCreationEnabled: Boolean + contactAutoCreationPolicy: MessageChannelContactAutoCreationPolicy + messageFolderImportPolicy: MessageFolderImportPolicy + isSyncEnabled: Boolean + excludeNonProfessionalEmails: Boolean + excludeGroupEmails: Boolean +} + +input CreateEmailGroupChannelInput { + handle: String! +} + +input CreateEmailingDomainInput { + domain: String! +} + input RunAgentInput { agentUniversalIdentifier: String! prompt: String! @@ -4291,99 +4482,6 @@ input UpdateWebhookInputUpdates { secret: String } -input CreateOneFieldMetadataInput { - """The record to create""" - field: CreateFieldInput! -} - -input CreateFieldInput { - type: FieldMetadataType! - name: String! - label: String! - description: String - icon: String - isActive: Boolean - isSystem: Boolean - isUIEditable: Boolean - isUIReadOnly: Boolean - isNullable: Boolean - isUnique: Boolean - defaultValue: JSON - options: JSON - settings: JSON - objectMetadataId: UUID! - isLabelSyncedWithName: Boolean - isRemoteCreation: Boolean - relationCreationPayload: JSON - morphRelationsCreationPayload: [JSON!] -} - -input UpdateOneFieldMetadataInput { - """The id of the record to update""" - id: UUID! - - """The record to update""" - update: UpdateFieldInput! -} - -input UpdateFieldInput { - universalIdentifier: String - name: String - label: String - description: String - icon: String - isActive: Boolean - isSystem: Boolean - isUIEditable: Boolean - isUIReadOnly: Boolean - isNullable: Boolean - isUnique: Boolean - defaultValue: JSON - options: JSON - settings: JSON - objectMetadataId: UUID - isLabelSyncedWithName: Boolean - morphRelationsUpdatePayload: [JSON!] -} - -input DeleteOneFieldInput { - """The id of the field to delete.""" - id: UUID! -} - -input CreateViewGroupInput { - id: UUID - isVisible: Boolean = true - fieldValue: String! - position: Float = 0 - viewId: UUID! -} - -input UpdateViewGroupInput { - """The id of the view group to update""" - id: UUID! - - """The view group to update""" - update: UpdateViewGroupInputUpdates! -} - -input UpdateViewGroupInputUpdates { - fieldMetadataId: UUID - isVisible: Boolean - fieldValue: String - position: Float -} - -input DeleteViewGroupInput { - """The id of the view group to delete.""" - id: UUID! -} - -input DestroyViewGroupInput { - """The id of the view group to destroy.""" - id: UUID! -} - input UpdateMessageFolderInput { id: UUID! update: UpdateMessageFolderInputUpdates! @@ -4444,100 +4542,11 @@ input SignUpInNewWorkspaceInput { subdomain: String } -input CreateApplicationRegistrationInput { - name: String! - universalIdentifier: String - oAuthRedirectUris: [String!] - oAuthScopes: [String!] -} - -input UpdateApplicationRegistrationInput { - id: String! - update: UpdateApplicationRegistrationPayload! -} - -input UpdateApplicationRegistrationPayload { - name: String - oAuthRedirectUris: [String!] - oAuthScopes: [String!] - isListed: Boolean - isPreInstalled: Boolean -} - -input CreateApplicationRegistrationVariableInput { - applicationRegistrationId: String! - key: String! - value: String! - description: String - isSecret: Boolean -} - -input UpdateApplicationRegistrationVariableInput { - id: String! - update: UpdateApplicationRegistrationVariablePayload! -} - -input UpdateApplicationRegistrationVariablePayload { - value: String - resetValue: Boolean - description: String -} - input UpdateWorkspaceMemberSettingsInput { workspaceMemberId: UUID! update: JSON! } -input ActivateWorkspaceInput { - """ - Deprecated: the workspace name is set at creation (signUpInNewWorkspace) and this field is ignored during activation. Kept for backward compatibility. - """ - displayName: String -} - -input UpdateWorkspaceInput { - subdomain: String - customDomain: String - displayName: String - logo: String - inviteHash: String - isPublicInviteLinkEnabled: Boolean - allowImpersonation: Boolean - isGoogleAuthEnabled: Boolean - isMicrosoftAuthEnabled: Boolean - isPasswordAuthEnabled: Boolean - isGoogleAuthBypassEnabled: Boolean - isMicrosoftAuthBypassEnabled: Boolean - isPasswordAuthBypassEnabled: Boolean - defaultRoleId: UUID - isTwoFactorAuthenticationEnforced: Boolean - trashRetentionDays: Float - eventLogRetentionDays: Float - fastModel: String - smartModel: String - aiAdditionalInstructions: String - editableProfileFields: [String!] - enabledAiModelIds: [String!] - useRecommendedModels: Boolean - isInternalMessagesImportEnabled: Boolean -} - -input WorkspaceMigrationInput { - actions: [WorkspaceMigrationDeleteActionInput!]! -} - -input WorkspaceMigrationDeleteActionInput { - type: WorkspaceMigrationActionType! - metadataName: AllMetadataName! - universalIdentifier: String! -} - -enum WorkspaceMigrationActionType { - delete - create - update -} - input SetupOIDCSsoInput { name: String! issuer: String! @@ -4568,6 +4577,20 @@ enum AnalyticsType { TRACK } +input CreateCalendarEventInput { + connectedAccountId: String! + title: String! + description: String + location: String + startsAt: String! + endsAt: String! + isFullDay: Boolean + timeZone: String + attendees: String + sendInvitations: Boolean + addConferencing: Boolean +} + input SendEmailInput { connectedAccountId: String! to: String! diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.ts b/packages/twenty-client-sdk/src/metadata/generated/schema.ts index e44e02177f..82047f3fef 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/schema.ts +++ b/packages/twenty-client-sdk/src/metadata/generated/schema.ts @@ -124,7 +124,7 @@ export interface UserWorkspace { __typename: 'UserWorkspace' } -export type PermissionFlagType = 'API_KEYS_AND_WEBHOOKS' | 'WORKSPACE' | 'WORKSPACE_MEMBERS' | 'ROLES' | 'DATA_MODEL' | 'SECURITY' | 'WORKFLOWS' | 'IMPERSONATE' | 'SSO_BYPASS' | 'APPLICATIONS' | 'MARKETPLACE_APPS' | 'LAYOUTS' | 'BILLING' | 'AI_SETTINGS' | 'AI' | 'VIEWS' | 'UPLOAD_FILE' | 'DOWNLOAD_FILE' | 'SEND_EMAIL_TOOL' | 'HTTP_REQUEST_TOOL' | 'CODE_INTERPRETER_TOOL' | 'IMPORT_CSV' | 'EXPORT_CSV' | 'CONNECTED_ACCOUNTS' | 'PROFILE_INFORMATION' +export type PermissionFlagType = 'API_KEYS_AND_WEBHOOKS' | 'WORKSPACE' | 'WORKSPACE_MEMBERS' | 'ROLES' | 'DATA_MODEL' | 'SECURITY' | 'WORKFLOWS' | 'IMPERSONATE' | 'SSO_BYPASS' | 'APPLICATIONS' | 'MARKETPLACE_APPS' | 'LAYOUTS' | 'BILLING' | 'AI_SETTINGS' | 'AI' | 'VIEWS' | 'UPLOAD_FILE' | 'DOWNLOAD_FILE' | 'SEND_EMAIL_TOOL' | 'CREATE_CALENDAR_EVENT_TOOL' | 'HTTP_REQUEST_TOOL' | 'CODE_INTERPRETER_TOOL' | 'IMPORT_CSV' | 'EXPORT_CSV' | 'CONNECTED_ACCOUNTS' | 'PROFILE_INFORMATION' export interface FullName { firstName: Scalars['String'] @@ -2359,6 +2359,14 @@ export interface ChannelSyncSuccess { __typename: 'ChannelSyncSuccess' } +export interface CreateCalendarEventOutput { + success: Scalars['Boolean'] + iCalUid?: Scalars['String'] + conferenceLink?: Scalars['String'] + error?: Scalars['String'] + __typename: 'CreateCalendarEventOutput' +} + export interface BarChartSeries { key: Scalars['String'] label: Scalars['String'] @@ -2696,21 +2704,33 @@ export interface Query { commandMenuItem?: CommandMenuItem frontComponents: FrontComponent[] frontComponent?: FrontComponent + currentWorkspace: Workspace + getPublicWorkspaceDataByDomain: PublicWorkspaceData + getPublicWorkspaceDataById: PublicWorkspaceDataSummary + field: Field + fields: FieldConnection + getViewGroups: ViewGroup[] + getViewGroup?: ViewGroup + findManyApplications: Application[] + findOneApplication: Application + getRoles: Role[] + findApplicationRegistrationByClientId?: PublicApplicationRegistration + findApplicationRegistrationByUniversalIdentifier?: ApplicationRegistration + findManyApplicationRegistrations: ApplicationRegistration[] + findOneApplicationRegistration: ApplicationRegistration + findApplicationRegistrationStats: ApplicationRegistrationStats + findApplicationRegistrationVariables: ApplicationRegistrationVariableDTO[] + applicationRegistrationTarballUrl?: Scalars['String'] previewMessageCampaignAudience: CampaignAudiencePreviewDTO unsubscribeTopics: UnsubscribeTopic[] unsubscribePagePreviewUrl: Scalars['String'] myMessageChannels: MessageChannel[] getEmailingDomains: EmailingDomain[] myConnectedAccounts: ConnectedAccountPublicDTO[] - getRoles: Role[] getToolIndex: ToolIndexEntry[] getToolInputSchema?: Scalars['JSON'] webhooks: Webhook[] webhook?: Webhook - field: Field - fields: FieldConnection - getViewGroups: ViewGroup[] - getViewGroup?: ViewGroup myMessageFolders: MessageFolder[] myCalendarChannels: CalendarChannel[] minimalMetadata: MinimalMetadata @@ -2731,19 +2751,7 @@ export interface Query { checkWorkspaceSubdomainAvailability: SubdomainAvailabilityDTO getWorkspaceCreationDefaults: WorkspaceCreationDefaultsDTO validatePasswordResetToken: ValidatePasswordResetToken - findApplicationRegistrationByClientId?: PublicApplicationRegistration - findApplicationRegistrationByUniversalIdentifier?: ApplicationRegistration - findManyApplicationRegistrations: ApplicationRegistration[] - findOneApplicationRegistration: ApplicationRegistration - findApplicationRegistrationStats: ApplicationRegistrationStats - findApplicationRegistrationVariables: ApplicationRegistrationVariableDTO[] - applicationRegistrationTarballUrl?: Scalars['String'] currentUser: User - currentWorkspace: Workspace - getPublicWorkspaceDataByDomain: PublicWorkspaceData - getPublicWorkspaceDataById: PublicWorkspaceDataSummary - findManyApplications: Application[] - findOneApplication: Application getSSOIdentityProviders: FindAvailableSSOIDP[] eventLogs: EventLogQueryResult pieChartData: PieChartData @@ -2864,6 +2872,40 @@ export interface Mutation { createFrontComponent: FrontComponent updateFrontComponent: FrontComponent deleteFrontComponent: FrontComponent + activateWorkspace: Workspace + updateWorkspace: Workspace + deleteCurrentWorkspace: Workspace + checkCustomDomainValidRecords?: DomainValidRecords + createOneField: Field + updateOneField: Field + deleteOneField: Field + createViewGroup: ViewGroup + createManyViewGroups: ViewGroup[] + updateViewGroup: ViewGroup + updateManyViewGroups: ViewGroup[] + deleteViewGroup: ViewGroup + destroyViewGroup: ViewGroup + runWorkspaceMigration: Scalars['Boolean'] + uninstallApplication: Scalars['Boolean'] + updateWorkspaceMemberRole: WorkspaceMember + createOneRole: Role + updateOneRole: Role + deleteOneRole: Scalars['String'] + upsertObjectPermissions: ObjectPermission[] + upsertPermissionFlags: RolePermissionFlag[] + upsertFieldPermissions: FieldPermission[] + upsertRowLevelPermissionPredicates: UpsertRowLevelPermissionPredicatesResult + assignRoleToAgent: Scalars['Boolean'] + removeRoleFromAgent: Scalars['Boolean'] + createApplicationRegistration: CreateApplicationRegistration + updateApplicationRegistration: ApplicationRegistration + deleteApplicationRegistration: Scalars['Boolean'] + rotateApplicationRegistrationClientSecret: RotateClientSecret + createApplicationRegistrationVariable: ApplicationRegistrationVariable + updateApplicationRegistrationVariable: ApplicationRegistrationVariable + deleteApplicationRegistrationVariable: Scalars['Boolean'] + uploadAppTarball: ApplicationRegistration + transferApplicationRegistrationOwnership: ApplicationRegistration sendEmailViaEmailingDomain: SendEmailViaDomainOutput sendMessageCampaign: SendMessageCampaignOutputDTO createUnsubscribeTopic: UnsubscribeTopic @@ -2876,29 +2918,10 @@ export interface Mutation { deleteEmailingDomain: Scalars['Boolean'] verifyEmailingDomain: EmailingDomain deleteConnectedAccount: ConnectedAccountPublicDTO - updateWorkspaceMemberRole: WorkspaceMember - createOneRole: Role - updateOneRole: Role - deleteOneRole: Scalars['String'] - upsertObjectPermissions: ObjectPermission[] - upsertPermissionFlags: RolePermissionFlag[] - upsertFieldPermissions: FieldPermission[] - upsertRowLevelPermissionPredicates: UpsertRowLevelPermissionPredicatesResult - assignRoleToAgent: Scalars['Boolean'] - removeRoleFromAgent: Scalars['Boolean'] runAgent: RunAgentResult createWebhook: Webhook updateWebhook: Webhook deleteWebhook: Webhook - createOneField: Field - updateOneField: Field - deleteOneField: Field - createViewGroup: ViewGroup - createManyViewGroups: ViewGroup[] - updateViewGroup: ViewGroup - updateManyViewGroups: ViewGroup[] - deleteViewGroup: ViewGroup - destroyViewGroup: ViewGroup updateMessageFolder: MessageFolder updateMessageFolders: MessageFolder[] updateCalendarChannel: CalendarChannel @@ -2935,15 +2958,6 @@ export interface Mutation { generatePlaygroundToken: AuthToken emailPasswordResetLink: EmailPasswordResetLink updatePasswordViaResetToken: InvalidatePassword - createApplicationRegistration: CreateApplicationRegistration - updateApplicationRegistration: ApplicationRegistration - deleteApplicationRegistration: Scalars['Boolean'] - rotateApplicationRegistrationClientSecret: RotateClientSecret - createApplicationRegistrationVariable: ApplicationRegistrationVariable - updateApplicationRegistrationVariable: ApplicationRegistrationVariable - deleteApplicationRegistrationVariable: Scalars['Boolean'] - uploadAppTarball: ApplicationRegistration - transferApplicationRegistrationOwnership: ApplicationRegistration initiateOTPProvisioning: InitiateTwoFactorAuthenticationProvisioning initiateOTPProvisioningForAuthenticatedUser: InitiateTwoFactorAuthenticationProvisioning deleteTwoFactorAuthenticationMethod: DeleteTwoFactorAuthenticationMethod @@ -2953,12 +2967,6 @@ export interface Mutation { updateWorkspaceMemberSettings: Scalars['Boolean'] updateUserEmail: Scalars['Boolean'] resendEmailVerificationToken: ResendEmailVerificationToken - activateWorkspace: Workspace - updateWorkspace: Workspace - deleteCurrentWorkspace: Workspace - checkCustomDomainValidRecords?: DomainValidRecords - runWorkspaceMigration: Scalars['Boolean'] - uninstallApplication: Scalars['Boolean'] createOIDCIdentityProvider: SetupSso createSAMLIdentityProvider: SetupSso deleteSSOIdentityProvider: DeleteSso @@ -2967,6 +2975,7 @@ export interface Mutation { trackAnalytics: Analytics duplicateDashboard: DuplicatedDashboard impersonate: Impersonate + createCalendarEvent: CreateCalendarEventOutput sendEmail: SendEmailOutput startChannelSync: ChannelSyncSuccess saveImapSmtpCaldavAccount: ImapSmtpCaldavConnectionSuccess @@ -5487,6 +5496,15 @@ export interface ChannelSyncSuccessGenqlSelection{ __scalar?: boolean | number } +export interface CreateCalendarEventOutputGenqlSelection{ + success?: boolean | number + iCalUid?: boolean | number + conferenceLink?: boolean | number + error?: boolean | number + __typename?: boolean | number + __scalar?: boolean | number +} + export interface BarChartSeriesGenqlSelection{ key?: boolean | number label?: boolean | number @@ -5854,17 +5872,9 @@ export interface QueryGenqlSelection{ commandMenuItem?: (CommandMenuItemGenqlSelection & { __args: {id: Scalars['UUID']} }) frontComponents?: FrontComponentGenqlSelection frontComponent?: (FrontComponentGenqlSelection & { __args: {id: Scalars['UUID']} }) - previewMessageCampaignAudience?: (CampaignAudiencePreviewDTOGenqlSelection & { __args: {input: PreviewMessageCampaignAudienceInput} }) - unsubscribeTopics?: UnsubscribeTopicGenqlSelection - unsubscribePagePreviewUrl?: boolean | number - myMessageChannels?: (MessageChannelGenqlSelection & { __args?: {connectedAccountId?: (Scalars['UUID'] | null)} }) - getEmailingDomains?: EmailingDomainGenqlSelection - myConnectedAccounts?: ConnectedAccountPublicDTOGenqlSelection - getRoles?: RoleGenqlSelection - getToolIndex?: ToolIndexEntryGenqlSelection - getToolInputSchema?: { __args: {toolName: Scalars['String']} } - webhooks?: WebhookGenqlSelection - webhook?: (WebhookGenqlSelection & { __args: {id: Scalars['UUID']} }) + currentWorkspace?: WorkspaceGenqlSelection + getPublicWorkspaceDataByDomain?: (PublicWorkspaceDataGenqlSelection & { __args?: {origin?: (Scalars['String'] | null)} }) + getPublicWorkspaceDataById?: (PublicWorkspaceDataSummaryGenqlSelection & { __args: {id: Scalars['UUID']} }) field?: (FieldGenqlSelection & { __args: { /** The id of the record to find. */ id: Scalars['UUID']} }) @@ -5875,6 +5885,26 @@ export interface QueryGenqlSelection{ filter: FieldFilter} }) getViewGroups?: (ViewGroupGenqlSelection & { __args?: {viewId?: (Scalars['String'] | null)} }) getViewGroup?: (ViewGroupGenqlSelection & { __args: {id: Scalars['String']} }) + findManyApplications?: ApplicationGenqlSelection + findOneApplication?: (ApplicationGenqlSelection & { __args?: {id?: (Scalars['UUID'] | null), universalIdentifier?: (Scalars['UUID'] | null)} }) + getRoles?: RoleGenqlSelection + findApplicationRegistrationByClientId?: (PublicApplicationRegistrationGenqlSelection & { __args: {clientId: Scalars['String']} }) + findApplicationRegistrationByUniversalIdentifier?: (ApplicationRegistrationGenqlSelection & { __args: {universalIdentifier: Scalars['String']} }) + findManyApplicationRegistrations?: ApplicationRegistrationGenqlSelection + findOneApplicationRegistration?: (ApplicationRegistrationGenqlSelection & { __args: {id: Scalars['String']} }) + findApplicationRegistrationStats?: (ApplicationRegistrationStatsGenqlSelection & { __args: {id: Scalars['String']} }) + findApplicationRegistrationVariables?: (ApplicationRegistrationVariableDTOGenqlSelection & { __args: {applicationRegistrationId: Scalars['String']} }) + applicationRegistrationTarballUrl?: { __args: {id: Scalars['String']} } + previewMessageCampaignAudience?: (CampaignAudiencePreviewDTOGenqlSelection & { __args: {input: PreviewMessageCampaignAudienceInput} }) + unsubscribeTopics?: UnsubscribeTopicGenqlSelection + unsubscribePagePreviewUrl?: boolean | number + myMessageChannels?: (MessageChannelGenqlSelection & { __args?: {connectedAccountId?: (Scalars['UUID'] | null)} }) + getEmailingDomains?: EmailingDomainGenqlSelection + myConnectedAccounts?: ConnectedAccountPublicDTOGenqlSelection + getToolIndex?: ToolIndexEntryGenqlSelection + getToolInputSchema?: { __args: {toolName: Scalars['String']} } + webhooks?: WebhookGenqlSelection + webhook?: (WebhookGenqlSelection & { __args: {id: Scalars['UUID']} }) myMessageFolders?: (MessageFolderGenqlSelection & { __args?: {messageChannelId?: (Scalars['UUID'] | null)} }) myCalendarChannels?: (CalendarChannelGenqlSelection & { __args?: {connectedAccountId?: (Scalars['UUID'] | null)} }) minimalMetadata?: MinimalMetadataGenqlSelection @@ -5895,19 +5925,7 @@ export interface QueryGenqlSelection{ checkWorkspaceSubdomainAvailability?: (SubdomainAvailabilityDTOGenqlSelection & { __args: {subdomain: Scalars['String']} }) getWorkspaceCreationDefaults?: WorkspaceCreationDefaultsDTOGenqlSelection validatePasswordResetToken?: (ValidatePasswordResetTokenGenqlSelection & { __args: {passwordResetToken: Scalars['String']} }) - findApplicationRegistrationByClientId?: (PublicApplicationRegistrationGenqlSelection & { __args: {clientId: Scalars['String']} }) - findApplicationRegistrationByUniversalIdentifier?: (ApplicationRegistrationGenqlSelection & { __args: {universalIdentifier: Scalars['String']} }) - findManyApplicationRegistrations?: ApplicationRegistrationGenqlSelection - findOneApplicationRegistration?: (ApplicationRegistrationGenqlSelection & { __args: {id: Scalars['String']} }) - findApplicationRegistrationStats?: (ApplicationRegistrationStatsGenqlSelection & { __args: {id: Scalars['String']} }) - findApplicationRegistrationVariables?: (ApplicationRegistrationVariableDTOGenqlSelection & { __args: {applicationRegistrationId: Scalars['String']} }) - applicationRegistrationTarballUrl?: { __args: {id: Scalars['String']} } currentUser?: UserGenqlSelection - currentWorkspace?: WorkspaceGenqlSelection - getPublicWorkspaceDataByDomain?: (PublicWorkspaceDataGenqlSelection & { __args?: {origin?: (Scalars['String'] | null)} }) - getPublicWorkspaceDataById?: (PublicWorkspaceDataSummaryGenqlSelection & { __args: {id: Scalars['UUID']} }) - findManyApplications?: ApplicationGenqlSelection - findOneApplication?: (ApplicationGenqlSelection & { __args?: {id?: (Scalars['UUID'] | null), universalIdentifier?: (Scalars['UUID'] | null)} }) getSSOIdentityProviders?: FindAvailableSSOIDPGenqlSelection eventLogs?: (EventLogQueryResultGenqlSelection & { __args: {input: EventLogQueryInput} }) pieChartData?: (PieChartDataGenqlSelection & { __args: {input: PieChartDataInput} }) @@ -6053,6 +6071,40 @@ export interface MutationGenqlSelection{ createFrontComponent?: (FrontComponentGenqlSelection & { __args: {input: CreateFrontComponentInput} }) updateFrontComponent?: (FrontComponentGenqlSelection & { __args: {input: UpdateFrontComponentInput} }) deleteFrontComponent?: (FrontComponentGenqlSelection & { __args: {id: Scalars['UUID']} }) + activateWorkspace?: (WorkspaceGenqlSelection & { __args: {data: ActivateWorkspaceInput} }) + updateWorkspace?: (WorkspaceGenqlSelection & { __args: {data: UpdateWorkspaceInput} }) + deleteCurrentWorkspace?: WorkspaceGenqlSelection + checkCustomDomainValidRecords?: DomainValidRecordsGenqlSelection + createOneField?: (FieldGenqlSelection & { __args: {input: CreateOneFieldMetadataInput} }) + updateOneField?: (FieldGenqlSelection & { __args: {input: UpdateOneFieldMetadataInput} }) + deleteOneField?: (FieldGenqlSelection & { __args: {input: DeleteOneFieldInput} }) + createViewGroup?: (ViewGroupGenqlSelection & { __args: {input: CreateViewGroupInput} }) + createManyViewGroups?: (ViewGroupGenqlSelection & { __args: {inputs: CreateViewGroupInput[]} }) + updateViewGroup?: (ViewGroupGenqlSelection & { __args: {input: UpdateViewGroupInput} }) + updateManyViewGroups?: (ViewGroupGenqlSelection & { __args: {inputs: UpdateViewGroupInput[]} }) + deleteViewGroup?: (ViewGroupGenqlSelection & { __args: {input: DeleteViewGroupInput} }) + destroyViewGroup?: (ViewGroupGenqlSelection & { __args: {input: DestroyViewGroupInput} }) + runWorkspaceMigration?: { __args: {workspaceMigration: WorkspaceMigrationInput} } + uninstallApplication?: { __args: {universalIdentifier: Scalars['String']} } + updateWorkspaceMemberRole?: (WorkspaceMemberGenqlSelection & { __args: {workspaceMemberId: Scalars['UUID'], roleId: Scalars['UUID']} }) + createOneRole?: (RoleGenqlSelection & { __args: {createRoleInput: CreateRoleInput} }) + updateOneRole?: (RoleGenqlSelection & { __args: {updateRoleInput: UpdateRoleInput} }) + deleteOneRole?: { __args: {roleId: Scalars['UUID']} } + upsertObjectPermissions?: (ObjectPermissionGenqlSelection & { __args: {upsertObjectPermissionsInput: UpsertObjectPermissionsInput} }) + upsertPermissionFlags?: (RolePermissionFlagGenqlSelection & { __args: {upsertPermissionFlagsInput: UpsertPermissionFlagsInput} }) + upsertFieldPermissions?: (FieldPermissionGenqlSelection & { __args: {upsertFieldPermissionsInput: UpsertFieldPermissionsInput} }) + upsertRowLevelPermissionPredicates?: (UpsertRowLevelPermissionPredicatesResultGenqlSelection & { __args: {input: UpsertRowLevelPermissionPredicatesInput} }) + assignRoleToAgent?: { __args: {agentId: Scalars['UUID'], roleId: Scalars['UUID']} } + removeRoleFromAgent?: { __args: {agentId: Scalars['UUID']} } + createApplicationRegistration?: (CreateApplicationRegistrationGenqlSelection & { __args: {input: CreateApplicationRegistrationInput} }) + updateApplicationRegistration?: (ApplicationRegistrationGenqlSelection & { __args: {input: UpdateApplicationRegistrationInput} }) + deleteApplicationRegistration?: { __args: {id: Scalars['String']} } + rotateApplicationRegistrationClientSecret?: (RotateClientSecretGenqlSelection & { __args: {id: Scalars['String']} }) + createApplicationRegistrationVariable?: (ApplicationRegistrationVariableGenqlSelection & { __args: {input: CreateApplicationRegistrationVariableInput} }) + updateApplicationRegistrationVariable?: (ApplicationRegistrationVariableGenqlSelection & { __args: {input: UpdateApplicationRegistrationVariableInput} }) + deleteApplicationRegistrationVariable?: { __args: {id: Scalars['String']} } + uploadAppTarball?: (ApplicationRegistrationGenqlSelection & { __args: {file: Scalars['Upload'], universalIdentifier?: (Scalars['String'] | null)} }) + transferApplicationRegistrationOwnership?: (ApplicationRegistrationGenqlSelection & { __args: {applicationRegistrationId: Scalars['String'], targetWorkspaceSubdomain: Scalars['String']} }) sendEmailViaEmailingDomain?: (SendEmailViaDomainOutputGenqlSelection & { __args: {input: SendEmailViaDomainInput} }) sendMessageCampaign?: (SendMessageCampaignOutputDTOGenqlSelection & { __args: {input: SendMessageCampaignInput} }) createUnsubscribeTopic?: (UnsubscribeTopicGenqlSelection & { __args: {input: CreateUnsubscribeTopicInput} }) @@ -6065,29 +6117,10 @@ export interface MutationGenqlSelection{ deleteEmailingDomain?: { __args: {id: Scalars['String']} } verifyEmailingDomain?: (EmailingDomainGenqlSelection & { __args: {id: Scalars['String']} }) deleteConnectedAccount?: (ConnectedAccountPublicDTOGenqlSelection & { __args: {id: Scalars['UUID']} }) - updateWorkspaceMemberRole?: (WorkspaceMemberGenqlSelection & { __args: {workspaceMemberId: Scalars['UUID'], roleId: Scalars['UUID']} }) - createOneRole?: (RoleGenqlSelection & { __args: {createRoleInput: CreateRoleInput} }) - updateOneRole?: (RoleGenqlSelection & { __args: {updateRoleInput: UpdateRoleInput} }) - deleteOneRole?: { __args: {roleId: Scalars['UUID']} } - upsertObjectPermissions?: (ObjectPermissionGenqlSelection & { __args: {upsertObjectPermissionsInput: UpsertObjectPermissionsInput} }) - upsertPermissionFlags?: (RolePermissionFlagGenqlSelection & { __args: {upsertPermissionFlagsInput: UpsertPermissionFlagsInput} }) - upsertFieldPermissions?: (FieldPermissionGenqlSelection & { __args: {upsertFieldPermissionsInput: UpsertFieldPermissionsInput} }) - upsertRowLevelPermissionPredicates?: (UpsertRowLevelPermissionPredicatesResultGenqlSelection & { __args: {input: UpsertRowLevelPermissionPredicatesInput} }) - assignRoleToAgent?: { __args: {agentId: Scalars['UUID'], roleId: Scalars['UUID']} } - removeRoleFromAgent?: { __args: {agentId: Scalars['UUID']} } runAgent?: (RunAgentResultGenqlSelection & { __args: {input: RunAgentInput} }) createWebhook?: (WebhookGenqlSelection & { __args: {input: CreateWebhookInput} }) updateWebhook?: (WebhookGenqlSelection & { __args: {input: UpdateWebhookInput} }) deleteWebhook?: (WebhookGenqlSelection & { __args: {id: Scalars['UUID']} }) - createOneField?: (FieldGenqlSelection & { __args: {input: CreateOneFieldMetadataInput} }) - updateOneField?: (FieldGenqlSelection & { __args: {input: UpdateOneFieldMetadataInput} }) - deleteOneField?: (FieldGenqlSelection & { __args: {input: DeleteOneFieldInput} }) - createViewGroup?: (ViewGroupGenqlSelection & { __args: {input: CreateViewGroupInput} }) - createManyViewGroups?: (ViewGroupGenqlSelection & { __args: {inputs: CreateViewGroupInput[]} }) - updateViewGroup?: (ViewGroupGenqlSelection & { __args: {input: UpdateViewGroupInput} }) - updateManyViewGroups?: (ViewGroupGenqlSelection & { __args: {inputs: UpdateViewGroupInput[]} }) - deleteViewGroup?: (ViewGroupGenqlSelection & { __args: {input: DeleteViewGroupInput} }) - destroyViewGroup?: (ViewGroupGenqlSelection & { __args: {input: DestroyViewGroupInput} }) updateMessageFolder?: (MessageFolderGenqlSelection & { __args: {input: UpdateMessageFolderInput} }) updateMessageFolders?: (MessageFolderGenqlSelection & { __args: {input: UpdateMessageFoldersInput} }) updateCalendarChannel?: (CalendarChannelGenqlSelection & { __args: {input: UpdateCalendarChannelInput} }) @@ -6124,15 +6157,6 @@ export interface MutationGenqlSelection{ generatePlaygroundToken?: AuthTokenGenqlSelection emailPasswordResetLink?: (EmailPasswordResetLinkGenqlSelection & { __args: {email: Scalars['String'], workspaceId?: (Scalars['UUID'] | null)} }) updatePasswordViaResetToken?: (InvalidatePasswordGenqlSelection & { __args: {passwordResetToken: Scalars['String'], newPassword: Scalars['String']} }) - createApplicationRegistration?: (CreateApplicationRegistrationGenqlSelection & { __args: {input: CreateApplicationRegistrationInput} }) - updateApplicationRegistration?: (ApplicationRegistrationGenqlSelection & { __args: {input: UpdateApplicationRegistrationInput} }) - deleteApplicationRegistration?: { __args: {id: Scalars['String']} } - rotateApplicationRegistrationClientSecret?: (RotateClientSecretGenqlSelection & { __args: {id: Scalars['String']} }) - createApplicationRegistrationVariable?: (ApplicationRegistrationVariableGenqlSelection & { __args: {input: CreateApplicationRegistrationVariableInput} }) - updateApplicationRegistrationVariable?: (ApplicationRegistrationVariableGenqlSelection & { __args: {input: UpdateApplicationRegistrationVariableInput} }) - deleteApplicationRegistrationVariable?: { __args: {id: Scalars['String']} } - uploadAppTarball?: (ApplicationRegistrationGenqlSelection & { __args: {file: Scalars['Upload'], universalIdentifier?: (Scalars['String'] | null)} }) - transferApplicationRegistrationOwnership?: (ApplicationRegistrationGenqlSelection & { __args: {applicationRegistrationId: Scalars['String'], targetWorkspaceSubdomain: Scalars['String']} }) initiateOTPProvisioning?: (InitiateTwoFactorAuthenticationProvisioningGenqlSelection & { __args: {loginToken: Scalars['String'], origin: Scalars['String']} }) initiateOTPProvisioningForAuthenticatedUser?: InitiateTwoFactorAuthenticationProvisioningGenqlSelection deleteTwoFactorAuthenticationMethod?: (DeleteTwoFactorAuthenticationMethodGenqlSelection & { __args: {twoFactorAuthenticationMethodId: Scalars['UUID']} }) @@ -6142,12 +6166,6 @@ export interface MutationGenqlSelection{ updateWorkspaceMemberSettings?: { __args: {input: UpdateWorkspaceMemberSettingsInput} } updateUserEmail?: { __args: {newEmail: Scalars['String'], verifyEmailRedirectPath?: (Scalars['String'] | null)} } resendEmailVerificationToken?: (ResendEmailVerificationTokenGenqlSelection & { __args: {email: Scalars['String'], origin: Scalars['String']} }) - activateWorkspace?: (WorkspaceGenqlSelection & { __args: {data: ActivateWorkspaceInput} }) - updateWorkspace?: (WorkspaceGenqlSelection & { __args: {data: UpdateWorkspaceInput} }) - deleteCurrentWorkspace?: WorkspaceGenqlSelection - checkCustomDomainValidRecords?: DomainValidRecordsGenqlSelection - runWorkspaceMigration?: { __args: {workspaceMigration: WorkspaceMigrationInput} } - uninstallApplication?: { __args: {universalIdentifier: Scalars['String']} } createOIDCIdentityProvider?: (SetupSsoGenqlSelection & { __args: {input: SetupOIDCSsoInput} }) createSAMLIdentityProvider?: (SetupSsoGenqlSelection & { __args: {input: SetupSAMLSsoInput} }) deleteSSOIdentityProvider?: (DeleteSsoGenqlSelection & { __args: {input: DeleteSsoInput} }) @@ -6156,6 +6174,7 @@ export interface MutationGenqlSelection{ trackAnalytics?: (AnalyticsGenqlSelection & { __args: {type: AnalyticsType, name?: (Scalars['String'] | null), event?: (Scalars['String'] | null), properties?: (Scalars['JSON'] | null)} }) duplicateDashboard?: (DuplicatedDashboardGenqlSelection & { __args: {id: Scalars['UUID']} }) impersonate?: (ImpersonateGenqlSelection & { __args: {userId: Scalars['UUID'], workspaceId: Scalars['UUID']} }) + createCalendarEvent?: (CreateCalendarEventOutputGenqlSelection & { __args: {input: CreateCalendarEventInput} }) sendEmail?: (SendEmailOutputGenqlSelection & { __args: {input: SendEmailInput} }) startChannelSync?: (ChannelSyncSuccessGenqlSelection & { __args: {connectedAccountId: Scalars['UUID']} }) saveImapSmtpCaldavAccount?: (ImapSmtpCaldavConnectionSuccessGenqlSelection & { __args: {handle: Scalars['String'], connectionParameters: EmailAccountConnectionParameters, id?: (Scalars['UUID'] | null)} }) @@ -6406,57 +6425,11 @@ update: UpdateFrontComponentInputUpdates} export interface UpdateFrontComponentInputUpdates {name?: (Scalars['String'] | null),description?: (Scalars['String'] | null)} -export interface SendEmailViaDomainInput {emailingDomainId: Scalars['String'],to: Scalars['String'][],cc?: (Scalars['String'][] | null),bcc?: (Scalars['String'][] | null),subject: Scalars['String'],text: Scalars['String'],html?: (Scalars['String'] | null),from: Scalars['String'],replyTo?: (Scalars['String'][] | null)} +export interface ActivateWorkspaceInput { +/** Deprecated: the workspace name is set at creation (signUpInNewWorkspace) and this field is ignored during activation. Kept for backward compatibility. */ +displayName?: (Scalars['String'] | null)} -export interface SendMessageCampaignInput {listId: Scalars['String'],unsubscribeTopicId?: (Scalars['String'] | null),subject: Scalars['String'],body: Scalars['String'],fromAddress: Scalars['String']} - -export interface CreateUnsubscribeTopicInput {name: Scalars['String'],description?: (Scalars['String'] | null),visibility?: (UnsubscribeTopicVisibility | null)} - -export interface UpdateUnsubscribeTopicInput {id: Scalars['String'],name?: (Scalars['String'] | null),description?: (Scalars['String'] | null),visibility?: (UnsubscribeTopicVisibility | null)} - -export interface UpdateMessageChannelInput {id: Scalars['UUID'],update: UpdateMessageChannelInputUpdates} - -export interface UpdateMessageChannelInputUpdates {visibility?: (MessageChannelVisibility | null),isContactAutoCreationEnabled?: (Scalars['Boolean'] | null),contactAutoCreationPolicy?: (MessageChannelContactAutoCreationPolicy | null),messageFolderImportPolicy?: (MessageFolderImportPolicy | null),isSyncEnabled?: (Scalars['Boolean'] | null),excludeNonProfessionalEmails?: (Scalars['Boolean'] | null),excludeGroupEmails?: (Scalars['Boolean'] | null)} - -export interface CreateEmailGroupChannelInput {handle: Scalars['String']} - -export interface CreateEmailingDomainInput {domain: Scalars['String']} - -export interface CreateRoleInput {id?: (Scalars['String'] | null),label: Scalars['String'],description?: (Scalars['String'] | null),icon?: (Scalars['String'] | null),canUpdateAllSettings?: (Scalars['Boolean'] | null),canAccessAllTools?: (Scalars['Boolean'] | null),canReadAllObjectRecords?: (Scalars['Boolean'] | null),canUpdateAllObjectRecords?: (Scalars['Boolean'] | null),canSoftDeleteAllObjectRecords?: (Scalars['Boolean'] | null),canDestroyAllObjectRecords?: (Scalars['Boolean'] | null),canBeAssignedToUsers?: (Scalars['Boolean'] | null),canBeAssignedToAgents?: (Scalars['Boolean'] | null),canBeAssignedToApiKeys?: (Scalars['Boolean'] | null)} - -export interface UpdateRoleInput {update: UpdateRolePayload, -/** The id of the role to update */ -id: Scalars['UUID']} - -export interface UpdateRolePayload {label?: (Scalars['String'] | null),description?: (Scalars['String'] | null),icon?: (Scalars['String'] | null),canUpdateAllSettings?: (Scalars['Boolean'] | null),canAccessAllTools?: (Scalars['Boolean'] | null),canReadAllObjectRecords?: (Scalars['Boolean'] | null),canUpdateAllObjectRecords?: (Scalars['Boolean'] | null),canSoftDeleteAllObjectRecords?: (Scalars['Boolean'] | null),canDestroyAllObjectRecords?: (Scalars['Boolean'] | null),canBeAssignedToUsers?: (Scalars['Boolean'] | null),canBeAssignedToAgents?: (Scalars['Boolean'] | null),canBeAssignedToApiKeys?: (Scalars['Boolean'] | null)} - -export interface UpsertObjectPermissionsInput {roleId: Scalars['UUID'],objectPermissions: ObjectPermissionInput[]} - -export interface ObjectPermissionInput {objectMetadataId: Scalars['UUID'],canReadObjectRecords?: (Scalars['Boolean'] | null),canUpdateObjectRecords?: (Scalars['Boolean'] | null),canSoftDeleteObjectRecords?: (Scalars['Boolean'] | null),canDestroyObjectRecords?: (Scalars['Boolean'] | null)} - -export interface UpsertPermissionFlagsInput {roleId: Scalars['UUID'],permissionFlagKeys: Scalars['String'][]} - -export interface UpsertFieldPermissionsInput {roleId: Scalars['UUID'],fieldPermissions: FieldPermissionInput[]} - -export interface FieldPermissionInput {objectMetadataId: Scalars['UUID'],fieldMetadataId: Scalars['UUID'],canReadFieldValue?: (Scalars['Boolean'] | null),canUpdateFieldValue?: (Scalars['Boolean'] | null)} - -export interface UpsertRowLevelPermissionPredicatesInput {roleId: Scalars['UUID'],objectMetadataId: Scalars['UUID'],predicates: RowLevelPermissionPredicateInput[],predicateGroups: RowLevelPermissionPredicateGroupInput[]} - -export interface RowLevelPermissionPredicateInput {id?: (Scalars['UUID'] | null),fieldMetadataId: Scalars['UUID'],operand: RowLevelPermissionPredicateOperand,value?: (Scalars['JSON'] | null),subFieldName?: (Scalars['String'] | null),workspaceMemberFieldMetadataId?: (Scalars['String'] | null),workspaceMemberSubFieldName?: (Scalars['String'] | null),rowLevelPermissionPredicateGroupId?: (Scalars['UUID'] | null),positionInRowLevelPermissionPredicateGroup?: (Scalars['Float'] | null)} - -export interface RowLevelPermissionPredicateGroupInput {id?: (Scalars['UUID'] | null),objectMetadataId: Scalars['UUID'],parentRowLevelPermissionPredicateGroupId?: (Scalars['UUID'] | null),logicalOperator: RowLevelPermissionPredicateGroupLogicalOperator,positionInRowLevelPermissionPredicateGroup?: (Scalars['Float'] | null)} - -export interface RunAgentInput {agentUniversalIdentifier: Scalars['String'],prompt: Scalars['String']} - -export interface CreateWebhookInput {id?: (Scalars['UUID'] | null),targetUrl: Scalars['String'],operations: Scalars['String'][],description?: (Scalars['String'] | null),secret?: (Scalars['String'] | null)} - -export interface UpdateWebhookInput { -/** The id of the webhook to update */ -id: Scalars['UUID'], -/** The webhook fields to update */ -update: UpdateWebhookInputUpdates} - -export interface UpdateWebhookInputUpdates {targetUrl?: (Scalars['String'] | null),operations?: (Scalars['String'][] | null),description?: (Scalars['String'] | null),secret?: (Scalars['String'] | null)} +export interface UpdateWorkspaceInput {subdomain?: (Scalars['String'] | null),customDomain?: (Scalars['String'] | null),displayName?: (Scalars['String'] | null),logo?: (Scalars['String'] | null),inviteHash?: (Scalars['String'] | null),isPublicInviteLinkEnabled?: (Scalars['Boolean'] | null),allowImpersonation?: (Scalars['Boolean'] | null),isGoogleAuthEnabled?: (Scalars['Boolean'] | null),isMicrosoftAuthEnabled?: (Scalars['Boolean'] | null),isPasswordAuthEnabled?: (Scalars['Boolean'] | null),isGoogleAuthBypassEnabled?: (Scalars['Boolean'] | null),isMicrosoftAuthBypassEnabled?: (Scalars['Boolean'] | null),isPasswordAuthBypassEnabled?: (Scalars['Boolean'] | null),defaultRoleId?: (Scalars['UUID'] | null),isTwoFactorAuthenticationEnforced?: (Scalars['Boolean'] | null),trashRetentionDays?: (Scalars['Float'] | null),eventLogRetentionDays?: (Scalars['Float'] | null),fastModel?: (Scalars['String'] | null),smartModel?: (Scalars['String'] | null),aiAdditionalInstructions?: (Scalars['String'] | null),editableProfileFields?: (Scalars['String'][] | null),enabledAiModelIds?: (Scalars['String'][] | null),useRecommendedModels?: (Scalars['Boolean'] | null),isInternalMessagesImportEnabled?: (Scalars['Boolean'] | null)} export interface CreateOneFieldMetadataInput { /** The record to create */ @@ -6494,6 +6467,74 @@ export interface DestroyViewGroupInput { /** The id of the view group to destroy. */ id: Scalars['UUID']} +export interface WorkspaceMigrationInput {actions: WorkspaceMigrationDeleteActionInput[]} + +export interface WorkspaceMigrationDeleteActionInput {type: WorkspaceMigrationActionType,metadataName: AllMetadataName,universalIdentifier: Scalars['String']} + +export interface CreateRoleInput {id?: (Scalars['String'] | null),label: Scalars['String'],description?: (Scalars['String'] | null),icon?: (Scalars['String'] | null),canUpdateAllSettings?: (Scalars['Boolean'] | null),canAccessAllTools?: (Scalars['Boolean'] | null),canReadAllObjectRecords?: (Scalars['Boolean'] | null),canUpdateAllObjectRecords?: (Scalars['Boolean'] | null),canSoftDeleteAllObjectRecords?: (Scalars['Boolean'] | null),canDestroyAllObjectRecords?: (Scalars['Boolean'] | null),canBeAssignedToUsers?: (Scalars['Boolean'] | null),canBeAssignedToAgents?: (Scalars['Boolean'] | null),canBeAssignedToApiKeys?: (Scalars['Boolean'] | null)} + +export interface UpdateRoleInput {update: UpdateRolePayload, +/** The id of the role to update */ +id: Scalars['UUID']} + +export interface UpdateRolePayload {label?: (Scalars['String'] | null),description?: (Scalars['String'] | null),icon?: (Scalars['String'] | null),canUpdateAllSettings?: (Scalars['Boolean'] | null),canAccessAllTools?: (Scalars['Boolean'] | null),canReadAllObjectRecords?: (Scalars['Boolean'] | null),canUpdateAllObjectRecords?: (Scalars['Boolean'] | null),canSoftDeleteAllObjectRecords?: (Scalars['Boolean'] | null),canDestroyAllObjectRecords?: (Scalars['Boolean'] | null),canBeAssignedToUsers?: (Scalars['Boolean'] | null),canBeAssignedToAgents?: (Scalars['Boolean'] | null),canBeAssignedToApiKeys?: (Scalars['Boolean'] | null)} + +export interface UpsertObjectPermissionsInput {roleId: Scalars['UUID'],objectPermissions: ObjectPermissionInput[]} + +export interface ObjectPermissionInput {objectMetadataId: Scalars['UUID'],canReadObjectRecords?: (Scalars['Boolean'] | null),canUpdateObjectRecords?: (Scalars['Boolean'] | null),canSoftDeleteObjectRecords?: (Scalars['Boolean'] | null),canDestroyObjectRecords?: (Scalars['Boolean'] | null)} + +export interface UpsertPermissionFlagsInput {roleId: Scalars['UUID'],permissionFlagKeys: Scalars['String'][]} + +export interface UpsertFieldPermissionsInput {roleId: Scalars['UUID'],fieldPermissions: FieldPermissionInput[]} + +export interface FieldPermissionInput {objectMetadataId: Scalars['UUID'],fieldMetadataId: Scalars['UUID'],canReadFieldValue?: (Scalars['Boolean'] | null),canUpdateFieldValue?: (Scalars['Boolean'] | null)} + +export interface UpsertRowLevelPermissionPredicatesInput {roleId: Scalars['UUID'],objectMetadataId: Scalars['UUID'],predicates: RowLevelPermissionPredicateInput[],predicateGroups: RowLevelPermissionPredicateGroupInput[]} + +export interface RowLevelPermissionPredicateInput {id?: (Scalars['UUID'] | null),fieldMetadataId: Scalars['UUID'],operand: RowLevelPermissionPredicateOperand,value?: (Scalars['JSON'] | null),subFieldName?: (Scalars['String'] | null),workspaceMemberFieldMetadataId?: (Scalars['String'] | null),workspaceMemberSubFieldName?: (Scalars['String'] | null),rowLevelPermissionPredicateGroupId?: (Scalars['UUID'] | null),positionInRowLevelPermissionPredicateGroup?: (Scalars['Float'] | null)} + +export interface RowLevelPermissionPredicateGroupInput {id?: (Scalars['UUID'] | null),objectMetadataId: Scalars['UUID'],parentRowLevelPermissionPredicateGroupId?: (Scalars['UUID'] | null),logicalOperator: RowLevelPermissionPredicateGroupLogicalOperator,positionInRowLevelPermissionPredicateGroup?: (Scalars['Float'] | null)} + +export interface CreateApplicationRegistrationInput {name: Scalars['String'],universalIdentifier?: (Scalars['String'] | null),oAuthRedirectUris?: (Scalars['String'][] | null),oAuthScopes?: (Scalars['String'][] | null)} + +export interface UpdateApplicationRegistrationInput {id: Scalars['String'],update: UpdateApplicationRegistrationPayload} + +export interface UpdateApplicationRegistrationPayload {name?: (Scalars['String'] | null),oAuthRedirectUris?: (Scalars['String'][] | null),oAuthScopes?: (Scalars['String'][] | null),isListed?: (Scalars['Boolean'] | null),isPreInstalled?: (Scalars['Boolean'] | null)} + +export interface CreateApplicationRegistrationVariableInput {applicationRegistrationId: Scalars['String'],key: Scalars['String'],value: Scalars['String'],description?: (Scalars['String'] | null),isSecret?: (Scalars['Boolean'] | null)} + +export interface UpdateApplicationRegistrationVariableInput {id: Scalars['String'],update: UpdateApplicationRegistrationVariablePayload} + +export interface UpdateApplicationRegistrationVariablePayload {value?: (Scalars['String'] | null),resetValue?: (Scalars['Boolean'] | null),description?: (Scalars['String'] | null)} + +export interface SendEmailViaDomainInput {emailingDomainId: Scalars['String'],to: Scalars['String'][],cc?: (Scalars['String'][] | null),bcc?: (Scalars['String'][] | null),subject: Scalars['String'],text: Scalars['String'],html?: (Scalars['String'] | null),from: Scalars['String'],replyTo?: (Scalars['String'][] | null)} + +export interface SendMessageCampaignInput {listId: Scalars['String'],unsubscribeTopicId?: (Scalars['String'] | null),subject: Scalars['String'],body: Scalars['String'],fromAddress: Scalars['String']} + +export interface CreateUnsubscribeTopicInput {name: Scalars['String'],description?: (Scalars['String'] | null),visibility?: (UnsubscribeTopicVisibility | null)} + +export interface UpdateUnsubscribeTopicInput {id: Scalars['String'],name?: (Scalars['String'] | null),description?: (Scalars['String'] | null),visibility?: (UnsubscribeTopicVisibility | null)} + +export interface UpdateMessageChannelInput {id: Scalars['UUID'],update: UpdateMessageChannelInputUpdates} + +export interface UpdateMessageChannelInputUpdates {visibility?: (MessageChannelVisibility | null),isContactAutoCreationEnabled?: (Scalars['Boolean'] | null),contactAutoCreationPolicy?: (MessageChannelContactAutoCreationPolicy | null),messageFolderImportPolicy?: (MessageFolderImportPolicy | null),isSyncEnabled?: (Scalars['Boolean'] | null),excludeNonProfessionalEmails?: (Scalars['Boolean'] | null),excludeGroupEmails?: (Scalars['Boolean'] | null)} + +export interface CreateEmailGroupChannelInput {handle: Scalars['String']} + +export interface CreateEmailingDomainInput {domain: Scalars['String']} + +export interface RunAgentInput {agentUniversalIdentifier: Scalars['String'],prompt: Scalars['String']} + +export interface CreateWebhookInput {id?: (Scalars['UUID'] | null),targetUrl: Scalars['String'],operations: Scalars['String'][],description?: (Scalars['String'] | null),secret?: (Scalars['String'] | null)} + +export interface UpdateWebhookInput { +/** The id of the webhook to update */ +id: Scalars['UUID'], +/** The webhook fields to update */ +update: UpdateWebhookInputUpdates} + +export interface UpdateWebhookInputUpdates {targetUrl?: (Scalars['String'] | null),operations?: (Scalars['String'][] | null),description?: (Scalars['String'] | null),secret?: (Scalars['String'] | null)} + export interface UpdateMessageFolderInput {id: Scalars['UUID'],update: UpdateMessageFolderInputUpdates} export interface UpdateMessageFolderInputUpdates {isSynced: Scalars['Boolean']} @@ -6514,30 +6555,8 @@ export interface GetAuthorizationUrlForSSOInput {identityProviderId: Scalars['UU export interface SignUpInNewWorkspaceInput {displayName?: (Scalars['String'] | null),subdomain?: (Scalars['String'] | null)} -export interface CreateApplicationRegistrationInput {name: Scalars['String'],universalIdentifier?: (Scalars['String'] | null),oAuthRedirectUris?: (Scalars['String'][] | null),oAuthScopes?: (Scalars['String'][] | null)} - -export interface UpdateApplicationRegistrationInput {id: Scalars['String'],update: UpdateApplicationRegistrationPayload} - -export interface UpdateApplicationRegistrationPayload {name?: (Scalars['String'] | null),oAuthRedirectUris?: (Scalars['String'][] | null),oAuthScopes?: (Scalars['String'][] | null),isListed?: (Scalars['Boolean'] | null),isPreInstalled?: (Scalars['Boolean'] | null)} - -export interface CreateApplicationRegistrationVariableInput {applicationRegistrationId: Scalars['String'],key: Scalars['String'],value: Scalars['String'],description?: (Scalars['String'] | null),isSecret?: (Scalars['Boolean'] | null)} - -export interface UpdateApplicationRegistrationVariableInput {id: Scalars['String'],update: UpdateApplicationRegistrationVariablePayload} - -export interface UpdateApplicationRegistrationVariablePayload {value?: (Scalars['String'] | null),resetValue?: (Scalars['Boolean'] | null),description?: (Scalars['String'] | null)} - export interface UpdateWorkspaceMemberSettingsInput {workspaceMemberId: Scalars['UUID'],update: Scalars['JSON']} -export interface ActivateWorkspaceInput { -/** Deprecated: the workspace name is set at creation (signUpInNewWorkspace) and this field is ignored during activation. Kept for backward compatibility. */ -displayName?: (Scalars['String'] | null)} - -export interface UpdateWorkspaceInput {subdomain?: (Scalars['String'] | null),customDomain?: (Scalars['String'] | null),displayName?: (Scalars['String'] | null),logo?: (Scalars['String'] | null),inviteHash?: (Scalars['String'] | null),isPublicInviteLinkEnabled?: (Scalars['Boolean'] | null),allowImpersonation?: (Scalars['Boolean'] | null),isGoogleAuthEnabled?: (Scalars['Boolean'] | null),isMicrosoftAuthEnabled?: (Scalars['Boolean'] | null),isPasswordAuthEnabled?: (Scalars['Boolean'] | null),isGoogleAuthBypassEnabled?: (Scalars['Boolean'] | null),isMicrosoftAuthBypassEnabled?: (Scalars['Boolean'] | null),isPasswordAuthBypassEnabled?: (Scalars['Boolean'] | null),defaultRoleId?: (Scalars['UUID'] | null),isTwoFactorAuthenticationEnforced?: (Scalars['Boolean'] | null),trashRetentionDays?: (Scalars['Float'] | null),eventLogRetentionDays?: (Scalars['Float'] | null),fastModel?: (Scalars['String'] | null),smartModel?: (Scalars['String'] | null),aiAdditionalInstructions?: (Scalars['String'] | null),editableProfileFields?: (Scalars['String'][] | null),enabledAiModelIds?: (Scalars['String'][] | null),useRecommendedModels?: (Scalars['Boolean'] | null),isInternalMessagesImportEnabled?: (Scalars['Boolean'] | null)} - -export interface WorkspaceMigrationInput {actions: WorkspaceMigrationDeleteActionInput[]} - -export interface WorkspaceMigrationDeleteActionInput {type: WorkspaceMigrationActionType,metadataName: AllMetadataName,universalIdentifier: Scalars['String']} - export interface SetupOIDCSsoInput {name: Scalars['String'],issuer: Scalars['String'],clientID: Scalars['String'],clientSecret: Scalars['String']} export interface SetupSAMLSsoInput {name: Scalars['String'],issuer: Scalars['String'],id: Scalars['UUID'],ssoURL: Scalars['String'],certificate: Scalars['String'],fingerprint?: (Scalars['String'] | null)} @@ -6546,6 +6565,8 @@ export interface DeleteSsoInput {identityProviderId: Scalars['UUID']} export interface EditSsoInput {id: Scalars['UUID'],status: SSOIdentityProviderStatus} +export interface CreateCalendarEventInput {connectedAccountId: Scalars['String'],title: Scalars['String'],description?: (Scalars['String'] | null),location?: (Scalars['String'] | null),startsAt: Scalars['String'],endsAt: Scalars['String'],isFullDay?: (Scalars['Boolean'] | null),timeZone?: (Scalars['String'] | null),attendees?: (Scalars['String'] | null),sendInvitations?: (Scalars['Boolean'] | null),addConferencing?: (Scalars['Boolean'] | null)} + export interface SendEmailInput {connectedAccountId: Scalars['String'],to: Scalars['String'],cc?: (Scalars['String'] | null),bcc?: (Scalars['String'] | null),subject: Scalars['String'],body: Scalars['String'],inReplyTo?: (Scalars['String'] | null),files?: (SendEmailAttachmentInput[] | null)} export interface SendEmailAttachmentInput {id: Scalars['String'],name: Scalars['String']} @@ -8358,6 +8379,14 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null + const CreateCalendarEventOutput_possibleTypes: string[] = ['CreateCalendarEventOutput'] + export const isCreateCalendarEventOutput = (obj?: { __typename?: any } | null): obj is CreateCalendarEventOutput => { + if (!obj?.__typename) throw new Error('__typename is missing in "isCreateCalendarEventOutput"') + return CreateCalendarEventOutput_possibleTypes.includes(obj.__typename) + } + + + const BarChartSeries_possibleTypes: string[] = ['BarChartSeries'] export const isBarChartSeries = (obj?: { __typename?: any } | null): obj is BarChartSeries => { if (!obj?.__typename) throw new Error('__typename is missing in "isBarChartSeries"') @@ -8672,6 +8701,7 @@ export const enumPermissionFlagType = { UPLOAD_FILE: 'UPLOAD_FILE' as const, DOWNLOAD_FILE: 'DOWNLOAD_FILE' as const, SEND_EMAIL_TOOL: 'SEND_EMAIL_TOOL' as const, + CREATE_CALENDAR_EVENT_TOOL: 'CREATE_CALENDAR_EVENT_TOOL' as const, HTTP_REQUEST_TOOL: 'HTTP_REQUEST_TOOL' as const, CODE_INTERPRETER_TOOL: 'CODE_INTERPRETER_TOOL' as const, IMPORT_CSV: 'IMPORT_CSV' as const, diff --git a/packages/twenty-client-sdk/src/metadata/generated/types.ts b/packages/twenty-client-sdk/src/metadata/generated/types.ts index 007f133751..67d1ce4c17 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/types.ts +++ b/packages/twenty-client-sdk/src/metadata/generated/types.ts @@ -71,18 +71,18 @@ export default { 280, 281, 288, - 326, 327, 328, 329, - 331, - 333, - 344, - 351, - 358, - 481, - 486, - 494 + 330, + 332, + 334, + 345, + 352, + 359, + 442, + 487, + 496 ], "types": { "BillingProductDTO": { @@ -5339,6 +5339,23 @@ export default { 1 ] }, + "CreateCalendarEventOutput": { + "success": [ + 6 + ], + "iCalUid": [ + 1 + ], + "conferenceLink": [ + 1 + ], + "error": [ + 1 + ], + "__typename": [ + 1 + ] + }, "BarChartSeries": { "key": [ 1 @@ -5361,7 +5378,7 @@ export default { 1 ], "series": [ - 301 + 302 ], "xAxisLabel": [ 1 @@ -5410,7 +5427,7 @@ export default { 1 ], "data": [ - 303 + 304 ], "__typename": [ 1 @@ -5418,7 +5435,7 @@ export default { }, "LineChartData": { "series": [ - 304 + 305 ], "xAxisLabel": [ 1 @@ -5455,7 +5472,7 @@ export default { }, "PieChartData": { "data": [ - 306 + 307 ], "showLegend": [ 6 @@ -5557,13 +5574,13 @@ export default { }, "EventLogQueryResult": { "records": [ - 311 + 312 ], "totalCount": [ 21 ], "pageInfo": [ - 312 + 313 ], "__typename": [ 1 @@ -5696,7 +5713,7 @@ export default { }, "AiSystemPromptPreview": { "sections": [ - 317 + 318 ], "estimatedTokenCount": [ 21 @@ -5772,10 +5789,10 @@ export default { 3 ], "evaluations": [ - 322 + 323 ], "messages": [ - 315 + 316 ], "createdAt": [ 4 @@ -5806,19 +5823,19 @@ export default { 1 ], "syncStatus": [ - 326 - ], - "syncStage": [ 327 ], - "visibility": [ + "syncStage": [ 328 ], + "visibility": [ + 329 + ], "isContactAutoCreationEnabled": [ 6 ], "contactAutoCreationPolicy": [ - 329 + 330 ], "isSyncEnabled": [ 6 @@ -5869,7 +5886,7 @@ export default { 1 ], "pendingSyncAction": [ - 331 + 332 ], "messageChannelId": [ 3 @@ -5887,7 +5904,7 @@ export default { "MessageFolderPendingSyncAction": {}, "CollectionHash": { "collectionName": [ - 333 + 334 ], "hash": [ 1 @@ -5951,13 +5968,13 @@ export default { }, "MinimalMetadata": { "objectMetadataItems": [ - 334 - ], - "views": [ 335 ], + "views": [ + 336 + ], "collectionHashes": [ - 332 + 333 ], "__typename": [ 1 @@ -6110,7 +6127,7 @@ export default { 2, { "input": [ - 338, + 339, "GetApiKeyInput!" ] } @@ -6213,7 +6230,7 @@ export default { 25, { "input": [ - 339, + 340, "AgentIdInput!" ] } @@ -6269,7 +6286,7 @@ export default { 41, { "input": [ - 340, + 341, "LogicFunctionIdInput!" ] } @@ -6281,7 +6298,7 @@ export default { 15, { "input": [ - 340, + 341, "LogicFunctionIdInput!" ] } @@ -6290,7 +6307,7 @@ export default { 1, { "input": [ - 340, + 341, "LogicFunctionIdInput!" ] } @@ -6319,55 +6336,19 @@ export default { ] } ], - "previewMessageCampaignAudience": [ - 283, + "currentWorkspace": [ + 76 + ], + "getPublicWorkspaceDataByDomain": [ + 181, { - "input": [ - 341, - "PreviewMessageCampaignAudienceInput!" + "origin": [ + 1 ] } ], - "unsubscribeTopics": [ - 287 - ], - "unsubscribePagePreviewUrl": [ - 1 - ], - "myMessageChannels": [ - 274, - { - "connectedAccountId": [ - 3 - ] - } - ], - "getEmailingDomains": [ - 272 - ], - "myConnectedAccounts": [ - 239 - ], - "getRoles": [ - 29 - ], - "getToolIndex": [ - 297 - ], - "getToolInputSchema": [ - 15, - { - "toolName": [ - 1, - "String!" - ] - } - ], - "webhooks": [ - 296 - ], - "webhook": [ - 296, + "getPublicWorkspaceDataById": [ + 182, { "id": [ 3, @@ -6414,149 +6395,22 @@ export default { ] } ], - "myMessageFolders": [ - 330, + "findManyApplications": [ + 59 + ], + "findOneApplication": [ + 59, { - "messageChannelId": [ + "id": [ 3 - ] - } - ], - "myCalendarChannels": [ - 325, - { - "connectedAccountId": [ - 3 - ] - } - ], - "minimalMetadata": [ - 336 - ], - "appConnections": [ - 218, - { - "filter": [ - 342 - ] - } - ], - "appConnection": [ - 218, - { - "id": [ - 219, - "ID!" - ] - } - ], - "findWorkspaceAiStats": [ - 324 - ], - "chatThreads": [ - 316 - ], - "chatThread": [ - 316, - { - "id": [ - 3, - "UUID!" - ] - } - ], - "chatMessages": [ - 315, - { - "threadId": [ - 3, - "UUID!" - ] - } - ], - "chatStreamCatchupChunks": [ - 319, - { - "threadId": [ - 3, - "UUID!" - ] - } - ], - "getAiSystemPromptPreview": [ - 318 - ], - "skills": [ - 314 - ], - "skill": [ - 314, - { - "id": [ - 3, - "UUID!" - ] - } - ], - "agentTurns": [ - 323, - { - "agentId": [ - 3, - "UUID!" - ] - } - ], - "checkUserExists": [ - 259, - { - "email": [ - 1, - "String!" ], - "captchaToken": [ - 1 + "universalIdentifier": [ + 3 ] } ], - "checkWorkspaceInviteHashIsValid": [ - 260, - { - "inviteHash": [ - 1, - "String!" - ] - } - ], - "findWorkspaceFromInviteHash": [ - 76, - { - "inviteHash": [ - 1, - "String!" - ] - } - ], - "checkWorkspaceSubdomainAvailability": [ - 254, - { - "subdomain": [ - 1, - "String!" - ] - } - ], - "getWorkspaceCreationDefaults": [ - 255 - ], - "validatePasswordResetToken": [ - 252, - { - "passwordResetToken": [ - 1, - "String!" - ] - } + "getRoles": [ + 29 ], "findApplicationRegistrationByClientId": [ 201, @@ -6615,22 +6469,52 @@ export default { ] } ], - "currentUser": [ - 79 - ], - "currentWorkspace": [ - 76 - ], - "getPublicWorkspaceDataByDomain": [ - 181, + "previewMessageCampaignAudience": [ + 283, { - "origin": [ - 1 + "input": [ + 342, + "PreviewMessageCampaignAudienceInput!" ] } ], - "getPublicWorkspaceDataById": [ - 182, + "unsubscribeTopics": [ + 287 + ], + "unsubscribePagePreviewUrl": [ + 1 + ], + "myMessageChannels": [ + 274, + { + "connectedAccountId": [ + 3 + ] + } + ], + "getEmailingDomains": [ + 272 + ], + "myConnectedAccounts": [ + 239 + ], + "getToolIndex": [ + 297 + ], + "getToolInputSchema": [ + 15, + { + "toolName": [ + 1, + "String!" + ] + } + ], + "webhooks": [ + 296 + ], + "webhook": [ + 296, { "id": [ 3, @@ -6638,55 +6522,188 @@ export default { ] } ], - "findManyApplications": [ - 59 - ], - "findOneApplication": [ - 59, + "myMessageFolders": [ + 331, { - "id": [ - 3 - ], - "universalIdentifier": [ + "messageChannelId": [ 3 ] } ], + "myCalendarChannels": [ + 326, + { + "connectedAccountId": [ + 3 + ] + } + ], + "minimalMetadata": [ + 337 + ], + "appConnections": [ + 218, + { + "filter": [ + 343 + ] + } + ], + "appConnection": [ + 218, + { + "id": [ + 219, + "ID!" + ] + } + ], + "findWorkspaceAiStats": [ + 325 + ], + "chatThreads": [ + 317 + ], + "chatThread": [ + 317, + { + "id": [ + 3, + "UUID!" + ] + } + ], + "chatMessages": [ + 316, + { + "threadId": [ + 3, + "UUID!" + ] + } + ], + "chatStreamCatchupChunks": [ + 320, + { + "threadId": [ + 3, + "UUID!" + ] + } + ], + "getAiSystemPromptPreview": [ + 319 + ], + "skills": [ + 315 + ], + "skill": [ + 315, + { + "id": [ + 3, + "UUID!" + ] + } + ], + "agentTurns": [ + 324, + { + "agentId": [ + 3, + "UUID!" + ] + } + ], + "checkUserExists": [ + 259, + { + "email": [ + 1, + "String!" + ], + "captchaToken": [ + 1 + ] + } + ], + "checkWorkspaceInviteHashIsValid": [ + 260, + { + "inviteHash": [ + 1, + "String!" + ] + } + ], + "findWorkspaceFromInviteHash": [ + 76, + { + "inviteHash": [ + 1, + "String!" + ] + } + ], + "checkWorkspaceSubdomainAvailability": [ + 254, + { + "subdomain": [ + 1, + "String!" + ] + } + ], + "getWorkspaceCreationDefaults": [ + 255 + ], + "validatePasswordResetToken": [ + 252, + { + "passwordResetToken": [ + 1, + "String!" + ] + } + ], + "currentUser": [ + 79 + ], "getSSOIdentityProviders": [ 224 ], "eventLogs": [ - 313, + 314, { "input": [ - 343, + 344, "EventLogQueryInput!" ] } ], "pieChartData": [ - 307, + 308, { "input": [ - 347, + 348, "PieChartDataInput!" ] } ], "lineChartData": [ - 305, + 306, { "input": [ - 348, + 349, "LineChartDataInput!" ] } ], "barChartData": [ - 302, + 303, { "input": [ - 349, + 350, "BarChartDataInput!" ] } @@ -6736,7 +6753,7 @@ export default { 264, { "input": [ - 350 + 351 ] } ], @@ -6810,10 +6827,10 @@ export default { }, "EventLogQueryInput": { "table": [ - 344 + 345 ], "filters": [ - 345 + 346 ], "first": [ 21 @@ -6834,7 +6851,7 @@ export default { 1 ], "dateRange": [ - 346 + 347 ], "recordId": [ 1 @@ -6901,7 +6918,7 @@ export default { 1 ], "operationTypes": [ - 351 + 352 ], "__typename": [ 1 @@ -6913,7 +6930,7 @@ export default { 6, { "input": [ - 353, + 354, "AddQuerySubscriptionInput!" ] } @@ -6922,7 +6939,7 @@ export default { 6, { "input": [ - 354, + 355, "RemoveQueryFromEventStreamInput!" ] } @@ -6931,7 +6948,7 @@ export default { 160, { "inputs": [ - 355, + 356, "[CreateNavigationMenuItemInput!]!" ] } @@ -6940,7 +6957,7 @@ export default { 160, { "input": [ - 355, + 356, "CreateNavigationMenuItemInput!" ] } @@ -6949,7 +6966,7 @@ export default { 160, { "inputs": [ - 356, + 357, "[UpdateOneNavigationMenuItemInput!]!" ] } @@ -6958,7 +6975,7 @@ export default { 160, { "input": [ - 356, + 357, "UpdateOneNavigationMenuItemInput!" ] } @@ -6985,7 +7002,7 @@ export default { 131, { "file": [ - 358, + 359, "Upload!" ] } @@ -7006,7 +7023,7 @@ export default { 131, { "file": [ - 358, + 359, "Upload!" ] } @@ -7015,7 +7032,7 @@ export default { 131, { "file": [ - 358, + 359, "Upload!" ] } @@ -7024,7 +7041,7 @@ export default { 131, { "file": [ - 358, + 359, "Upload!" ] } @@ -7033,7 +7050,7 @@ export default { 131, { "file": [ - 358, + 359, "Upload!" ] } @@ -7042,7 +7059,7 @@ export default { 131, { "file": [ - 358, + 359, "Upload!" ], "fieldMetadataId": [ @@ -7055,7 +7072,7 @@ export default { 131, { "file": [ - 358, + 359, "Upload!" ], "fieldMetadataUniversalIdentifier": [ @@ -7068,7 +7085,7 @@ export default { 62, { "input": [ - 359, + 360, "CreateViewFilterGroupInput!" ] } @@ -7081,7 +7098,7 @@ export default { "String!" ], "input": [ - 360, + 361, "UpdateViewFilterGroupInput!" ] } @@ -7108,7 +7125,7 @@ export default { 64, { "input": [ - 361, + 362, "CreateViewFilterInput!" ] } @@ -7117,7 +7134,7 @@ export default { 64, { "input": [ - 362, + 363, "UpdateViewFilterInput!" ] } @@ -7126,7 +7143,7 @@ export default { 64, { "input": [ - 364, + 365, "DeleteViewFilterInput!" ] } @@ -7135,7 +7152,7 @@ export default { 64, { "input": [ - 365, + 366, "DestroyViewFilterInput!" ] } @@ -7144,7 +7161,7 @@ export default { 70, { "input": [ - 366, + 367, "CreateViewInput!" ] } @@ -7157,7 +7174,7 @@ export default { "String!" ], "input": [ - 367, + 368, "UpdateViewInput!" ] } @@ -7184,7 +7201,7 @@ export default { 70, { "input": [ - 368, + 369, "UpsertViewWidgetInput!" ] } @@ -7193,7 +7210,7 @@ export default { 67, { "input": [ - 373, + 374, "CreateViewSortInput!" ] } @@ -7202,7 +7219,7 @@ export default { 67, { "input": [ - 374, + 375, "UpdateViewSortInput!" ] } @@ -7211,7 +7228,7 @@ export default { 6, { "input": [ - 376, + 377, "DeleteViewSortInput!" ] } @@ -7220,7 +7237,7 @@ export default { 6, { "input": [ - 377, + 378, "DestroyViewSortInput!" ] } @@ -7229,7 +7246,7 @@ export default { 60, { "input": [ - 378, + 379, "UpdateViewFieldInput!" ] } @@ -7238,7 +7255,7 @@ export default { 60, { "input": [ - 380, + 381, "CreateViewFieldInput!" ] } @@ -7247,7 +7264,7 @@ export default { 60, { "inputs": [ - 380, + 381, "[CreateViewFieldInput!]!" ] } @@ -7256,7 +7273,7 @@ export default { 60, { "input": [ - 381, + 382, "DeleteViewFieldInput!" ] } @@ -7265,7 +7282,7 @@ export default { 60, { "input": [ - 382, + 383, "DestroyViewFieldInput!" ] } @@ -7274,7 +7291,7 @@ export default { 69, { "input": [ - 383, + 384, "UpdateViewFieldGroupInput!" ] } @@ -7283,7 +7300,7 @@ export default { 69, { "input": [ - 385, + 386, "CreateViewFieldGroupInput!" ] } @@ -7292,7 +7309,7 @@ export default { 69, { "inputs": [ - 385, + 386, "[CreateViewFieldGroupInput!]!" ] } @@ -7301,7 +7318,7 @@ export default { 69, { "input": [ - 386, + 387, "DeleteViewFieldGroupInput!" ] } @@ -7310,7 +7327,7 @@ export default { 69, { "input": [ - 387, + 388, "DestroyViewFieldGroupInput!" ] } @@ -7319,7 +7336,7 @@ export default { 70, { "input": [ - 388, + 389, "UpsertFieldsWidgetInput!" ] } @@ -7328,7 +7345,7 @@ export default { 2, { "input": [ - 391, + 392, "CreateApiKeyInput!" ] } @@ -7337,7 +7354,7 @@ export default { 2, { "input": [ - 392, + 393, "UpdateApiKeyInput!" ] } @@ -7346,7 +7363,7 @@ export default { 2, { "input": [ - 393, + 394, "RevokeApiKeyInput!" ] } @@ -7495,7 +7512,7 @@ export default { 130, { "input": [ - 394, + 395, "CreateApprovedAccessDomainInput!" ] } @@ -7504,7 +7521,7 @@ export default { 6, { "input": [ - 395, + 396, "DeleteApprovedAccessDomainInput!" ] } @@ -7513,7 +7530,7 @@ export default { 130, { "input": [ - 396, + 397, "ValidateApprovedAccessDomainInput!" ] } @@ -7522,7 +7539,7 @@ export default { 123, { "input": [ - 397, + 398, "CreatePageLayoutTabInput!" ] } @@ -7535,7 +7552,7 @@ export default { "String!" ], "input": [ - 398, + 399, "UpdatePageLayoutTabInput!" ] } @@ -7553,7 +7570,7 @@ export default { 124, { "input": [ - 399, + 400, "CreatePageLayoutInput!" ] } @@ -7566,7 +7583,7 @@ export default { "String!" ], "input": [ - 400, + 401, "UpdatePageLayoutInput!" ] } @@ -7588,7 +7605,7 @@ export default { "String!" ], "input": [ - 401, + 402, "UpdatePageLayoutWithTabsInput!" ] } @@ -7624,7 +7641,7 @@ export default { 85, { "input": [ - 405, + 406, "CreatePageLayoutWidgetInput!" ] } @@ -7637,7 +7654,7 @@ export default { "String!" ], "input": [ - 406, + 407, "UpdatePageLayoutWidgetInput!" ] } @@ -7655,7 +7672,7 @@ export default { 25, { "input": [ - 407, + 408, "CreateAgentInput!" ] } @@ -7664,7 +7681,7 @@ export default { 25, { "input": [ - 408, + 409, "UpdateAgentInput!" ] } @@ -7673,7 +7690,7 @@ export default { 25, { "input": [ - 339, + 340, "AgentIdInput!" ] } @@ -7682,7 +7699,7 @@ export default { 56, { "input": [ - 409, + 410, "CreateOneObjectInput!" ] } @@ -7691,7 +7708,7 @@ export default { 56, { "input": [ - 411, + 412, "DeleteOneObjectInput!" ] } @@ -7700,7 +7717,7 @@ export default { 56, { "input": [ - 412, + 413, "UpdateOneObjectInput!" ] } @@ -7709,7 +7726,7 @@ export default { 47, { "input": [ - 414, + 415, "CreateOneIndexInput!" ] } @@ -7718,7 +7735,7 @@ export default { 47, { "input": [ - 417, + 418, "DeleteOneIndexInput!" ] } @@ -7727,7 +7744,7 @@ export default { 41, { "input": [ - 340, + 341, "LogicFunctionIdInput!" ] } @@ -7736,7 +7753,7 @@ export default { 41, { "input": [ - 418, + 419, "CreateLogicFunctionFromSourceInput!" ] } @@ -7745,7 +7762,7 @@ export default { 169, { "input": [ - 419, + 420, "ExecuteOneLogicFunctionInput!" ] } @@ -7754,7 +7771,7 @@ export default { 6, { "input": [ - 420, + 421, "UpdateLogicFunctionFromSourceInput!" ] } @@ -7763,7 +7780,7 @@ export default { 35, { "input": [ - 422, + 423, "CreateCommandMenuItemInput!" ] } @@ -7772,7 +7789,7 @@ export default { 35, { "input": [ - 423, + 424, "UpdateCommandMenuItemInput!" ] } @@ -7799,7 +7816,7 @@ export default { 34, { "input": [ - 424, + 425, "CreateFrontComponentInput!" ] } @@ -7808,7 +7825,7 @@ export default { 34, { "input": [ - 425, + 426, "UpdateFrontComponentInput!" ] } @@ -7822,11 +7839,320 @@ export default { ] } ], + "activateWorkspace": [ + 76, + { + "data": [ + 428, + "ActivateWorkspaceInput!" + ] + } + ], + "updateWorkspace": [ + 76, + { + "data": [ + 429, + "UpdateWorkspaceInput!" + ] + } + ], + "deleteCurrentWorkspace": [ + 76 + ], + "checkCustomDomainValidRecords": [ + 233 + ], + "createOneField": [ + 44, + { + "input": [ + 430, + "CreateOneFieldMetadataInput!" + ] + } + ], + "updateOneField": [ + 44, + { + "input": [ + 432, + "UpdateOneFieldMetadataInput!" + ] + } + ], + "deleteOneField": [ + 44, + { + "input": [ + 434, + "DeleteOneFieldInput!" + ] + } + ], + "createViewGroup": [ + 66, + { + "input": [ + 435, + "CreateViewGroupInput!" + ] + } + ], + "createManyViewGroups": [ + 66, + { + "inputs": [ + 435, + "[CreateViewGroupInput!]!" + ] + } + ], + "updateViewGroup": [ + 66, + { + "input": [ + 436, + "UpdateViewGroupInput!" + ] + } + ], + "updateManyViewGroups": [ + 66, + { + "inputs": [ + 436, + "[UpdateViewGroupInput!]!" + ] + } + ], + "deleteViewGroup": [ + 66, + { + "input": [ + 438, + "DeleteViewGroupInput!" + ] + } + ], + "destroyViewGroup": [ + 66, + { + "input": [ + 439, + "DestroyViewGroupInput!" + ] + } + ], + "runWorkspaceMigration": [ + 6, + { + "workspaceMigration": [ + 440, + "WorkspaceMigrationInput!" + ] + } + ], + "uninstallApplication": [ + 6, + { + "universalIdentifier": [ + 1, + "String!" + ] + } + ], + "updateWorkspaceMemberRole": [ + 20, + { + "workspaceMemberId": [ + 3, + "UUID!" + ], + "roleId": [ + 3, + "UUID!" + ] + } + ], + "createOneRole": [ + 29, + { + "createRoleInput": [ + 443, + "CreateRoleInput!" + ] + } + ], + "updateOneRole": [ + 29, + { + "updateRoleInput": [ + 444, + "UpdateRoleInput!" + ] + } + ], + "deleteOneRole": [ + 1, + { + "roleId": [ + 3, + "UUID!" + ] + } + ], + "upsertObjectPermissions": [ + 16, + { + "upsertObjectPermissionsInput": [ + 446, + "UpsertObjectPermissionsInput!" + ] + } + ], + "upsertPermissionFlags": [ + 27, + { + "upsertPermissionFlagsInput": [ + 448, + "UpsertPermissionFlagsInput!" + ] + } + ], + "upsertFieldPermissions": [ + 26, + { + "upsertFieldPermissionsInput": [ + 449, + "UpsertFieldPermissionsInput!" + ] + } + ], + "upsertRowLevelPermissionPredicates": [ + 234, + { + "input": [ + 451, + "UpsertRowLevelPermissionPredicatesInput!" + ] + } + ], + "assignRoleToAgent": [ + 6, + { + "agentId": [ + 3, + "UUID!" + ], + "roleId": [ + 3, + "UUID!" + ] + } + ], + "removeRoleFromAgent": [ + 6, + { + "agentId": [ + 3, + "UUID!" + ] + } + ], + "createApplicationRegistration": [ + 200, + { + "input": [ + 454, + "CreateApplicationRegistrationInput!" + ] + } + ], + "updateApplicationRegistration": [ + 7, + { + "input": [ + 455, + "UpdateApplicationRegistrationInput!" + ] + } + ], + "deleteApplicationRegistration": [ + 6, + { + "id": [ + 1, + "String!" + ] + } + ], + "rotateApplicationRegistrationClientSecret": [ + 202, + { + "id": [ + 1, + "String!" + ] + } + ], + "createApplicationRegistrationVariable": [ + 5, + { + "input": [ + 457, + "CreateApplicationRegistrationVariableInput!" + ] + } + ], + "updateApplicationRegistrationVariable": [ + 5, + { + "input": [ + 458, + "UpdateApplicationRegistrationVariableInput!" + ] + } + ], + "deleteApplicationRegistrationVariable": [ + 6, + { + "id": [ + 1, + "String!" + ] + } + ], + "uploadAppTarball": [ + 7, + { + "file": [ + 359, + "Upload!" + ], + "universalIdentifier": [ + 1 + ] + } + ], + "transferApplicationRegistrationOwnership": [ + 7, + { + "applicationRegistrationId": [ + 1, + "String!" + ], + "targetWorkspaceSubdomain": [ + 1, + "String!" + ] + } + ], "sendEmailViaEmailingDomain": [ 284, { "input": [ - 427, + 460, "SendEmailViaDomainInput!" ] } @@ -7835,7 +8161,7 @@ export default { 286, { "input": [ - 428, + 461, "SendMessageCampaignInput!" ] } @@ -7844,7 +8170,7 @@ export default { 287, { "input": [ - 429, + 462, "CreateUnsubscribeTopicInput!" ] } @@ -7853,7 +8179,7 @@ export default { 287, { "input": [ - 430, + 463, "UpdateUnsubscribeTopicInput!" ] } @@ -7871,7 +8197,7 @@ export default { 274, { "input": [ - 431, + 464, "UpdateMessageChannelInput!" ] } @@ -7880,7 +8206,7 @@ export default { 282, { "input": [ - 433, + 466, "CreateEmailGroupChannelInput!" ] } @@ -7898,7 +8224,7 @@ export default { 272, { "input": [ - 434, + 467, "CreateEmailingDomainInput!" ] } @@ -7930,109 +8256,11 @@ export default { ] } ], - "updateWorkspaceMemberRole": [ - 20, - { - "workspaceMemberId": [ - 3, - "UUID!" - ], - "roleId": [ - 3, - "UUID!" - ] - } - ], - "createOneRole": [ - 29, - { - "createRoleInput": [ - 435, - "CreateRoleInput!" - ] - } - ], - "updateOneRole": [ - 29, - { - "updateRoleInput": [ - 436, - "UpdateRoleInput!" - ] - } - ], - "deleteOneRole": [ - 1, - { - "roleId": [ - 3, - "UUID!" - ] - } - ], - "upsertObjectPermissions": [ - 16, - { - "upsertObjectPermissionsInput": [ - 438, - "UpsertObjectPermissionsInput!" - ] - } - ], - "upsertPermissionFlags": [ - 27, - { - "upsertPermissionFlagsInput": [ - 440, - "UpsertPermissionFlagsInput!" - ] - } - ], - "upsertFieldPermissions": [ - 26, - { - "upsertFieldPermissionsInput": [ - 441, - "UpsertFieldPermissionsInput!" - ] - } - ], - "upsertRowLevelPermissionPredicates": [ - 234, - { - "input": [ - 443, - "UpsertRowLevelPermissionPredicatesInput!" - ] - } - ], - "assignRoleToAgent": [ - 6, - { - "agentId": [ - 3, - "UUID!" - ], - "roleId": [ - 3, - "UUID!" - ] - } - ], - "removeRoleFromAgent": [ - 6, - { - "agentId": [ - 3, - "UUID!" - ] - } - ], "runAgent": [ 299, { "input": [ - 446, + 468, "RunAgentInput!" ] } @@ -8041,7 +8269,7 @@ export default { 296, { "input": [ - 447, + 469, "CreateWebhookInput!" ] } @@ -8050,7 +8278,7 @@ export default { 296, { "input": [ - 448, + 470, "UpdateWebhookInput!" ] } @@ -8064,119 +8292,38 @@ export default { ] } ], - "createOneField": [ - 44, - { - "input": [ - 450, - "CreateOneFieldMetadataInput!" - ] - } - ], - "updateOneField": [ - 44, - { - "input": [ - 452, - "UpdateOneFieldMetadataInput!" - ] - } - ], - "deleteOneField": [ - 44, - { - "input": [ - 454, - "DeleteOneFieldInput!" - ] - } - ], - "createViewGroup": [ - 66, - { - "input": [ - 455, - "CreateViewGroupInput!" - ] - } - ], - "createManyViewGroups": [ - 66, - { - "inputs": [ - 455, - "[CreateViewGroupInput!]!" - ] - } - ], - "updateViewGroup": [ - 66, - { - "input": [ - 456, - "UpdateViewGroupInput!" - ] - } - ], - "updateManyViewGroups": [ - 66, - { - "inputs": [ - 456, - "[UpdateViewGroupInput!]!" - ] - } - ], - "deleteViewGroup": [ - 66, - { - "input": [ - 458, - "DeleteViewGroupInput!" - ] - } - ], - "destroyViewGroup": [ - 66, - { - "input": [ - 459, - "DestroyViewGroupInput!" - ] - } - ], "updateMessageFolder": [ - 330, + 331, { "input": [ - 460, + 472, "UpdateMessageFolderInput!" ] } ], "updateMessageFolders": [ - 330, + 331, { "input": [ - 462, + 474, "UpdateMessageFoldersInput!" ] } ], "updateCalendarChannel": [ - 325, + 326, { "input": [ - 463, + 475, "UpdateCalendarChannelInput!" ] } ], "createChatThread": [ - 316 + 317 ], "sendChatMessage": [ - 320, + 321, { "threadId": [ 3, @@ -8197,7 +8344,7 @@ export default { 1 ], "fileAttachments": [ - 465, + 477, "[FileAttachmentInput!]" ] } @@ -8212,7 +8359,7 @@ export default { } ], "renameChatThread": [ - 316, + 317, { "id": [ 3, @@ -8225,7 +8372,7 @@ export default { } ], "archiveChatThread": [ - 316, + 317, { "id": [ 3, @@ -8234,7 +8381,7 @@ export default { } ], "unarchiveChatThread": [ - 316, + 317, { "id": [ 3, @@ -8261,25 +8408,25 @@ export default { } ], "createSkill": [ - 314, + 315, { "input": [ - 466, + 478, "CreateSkillInput!" ] } ], "updateSkill": [ - 314, + 315, { "input": [ - 467, + 479, "UpdateSkillInput!" ] } ], "deleteSkill": [ - 314, + 315, { "id": [ 3, @@ -8288,7 +8435,7 @@ export default { } ], "activateSkill": [ - 314, + 315, { "id": [ 3, @@ -8297,7 +8444,7 @@ export default { } ], "deactivateSkill": [ - 314, + 315, { "id": [ 3, @@ -8306,7 +8453,7 @@ export default { } ], "evaluateAgentTurn": [ - 322, + 323, { "turnId": [ 3, @@ -8315,7 +8462,7 @@ export default { } ], "runEvaluationInput": [ - 323, + 324, { "agentId": [ 3, @@ -8331,7 +8478,7 @@ export default { 247, { "input": [ - 468, + 480, "GetAuthorizationUrlForSSOInput!" ] } @@ -8497,7 +8644,7 @@ export default { 250, { "input": [ - 469 + 481 ] } ], @@ -8509,7 +8656,7 @@ export default { "String!" ], "file": [ - 358, + 359, "Upload!" ] } @@ -8602,94 +8749,6 @@ export default { ] } ], - "createApplicationRegistration": [ - 200, - { - "input": [ - 470, - "CreateApplicationRegistrationInput!" - ] - } - ], - "updateApplicationRegistration": [ - 7, - { - "input": [ - 471, - "UpdateApplicationRegistrationInput!" - ] - } - ], - "deleteApplicationRegistration": [ - 6, - { - "id": [ - 1, - "String!" - ] - } - ], - "rotateApplicationRegistrationClientSecret": [ - 202, - { - "id": [ - 1, - "String!" - ] - } - ], - "createApplicationRegistrationVariable": [ - 5, - { - "input": [ - 473, - "CreateApplicationRegistrationVariableInput!" - ] - } - ], - "updateApplicationRegistrationVariable": [ - 5, - { - "input": [ - 474, - "UpdateApplicationRegistrationVariableInput!" - ] - } - ], - "deleteApplicationRegistrationVariable": [ - 6, - { - "id": [ - 1, - "String!" - ] - } - ], - "uploadAppTarball": [ - 7, - { - "file": [ - 358, - "Upload!" - ], - "universalIdentifier": [ - 1 - ] - } - ], - "transferApplicationRegistrationOwnership": [ - 7, - { - "applicationRegistrationId": [ - 1, - "String!" - ], - "targetWorkspaceSubdomain": [ - 1, - "String!" - ] - } - ], "initiateOTPProvisioning": [ 241, { @@ -8740,7 +8799,7 @@ export default { 6, { "input": [ - 476, + 482, "UpdateWorkspaceMemberSettingsInput!" ] } @@ -8770,53 +8829,11 @@ export default { ] } ], - "activateWorkspace": [ - 76, - { - "data": [ - 477, - "ActivateWorkspaceInput!" - ] - } - ], - "updateWorkspace": [ - 76, - { - "data": [ - 478, - "UpdateWorkspaceInput!" - ] - } - ], - "deleteCurrentWorkspace": [ - 76 - ], - "checkCustomDomainValidRecords": [ - 233 - ], - "runWorkspaceMigration": [ - 6, - { - "workspaceMigration": [ - 479, - "WorkspaceMigrationInput!" - ] - } - ], - "uninstallApplication": [ - 6, - { - "universalIdentifier": [ - 1, - "String!" - ] - } - ], "createOIDCIdentityProvider": [ 225, { "input": [ - 482, + 483, "SetupOIDCSsoInput!" ] } @@ -8825,7 +8842,7 @@ export default { 225, { "input": [ - 483, + 484, "SetupSAMLSsoInput!" ] } @@ -8834,7 +8851,7 @@ export default { 221, { "input": [ - 484, + 485, "DeleteSsoInput!" ] } @@ -8843,13 +8860,13 @@ export default { 222, { "input": [ - 485, + 486, "EditSsoInput!" ] } ], "createObjectEvent": [ - 310, + 311, { "event": [ 1, @@ -8869,10 +8886,10 @@ export default { } ], "trackAnalytics": [ - 310, + 311, { "type": [ - 486, + 487, "AnalyticsType!" ], "name": [ @@ -8887,7 +8904,7 @@ export default { } ], "duplicateDashboard": [ - 308, + 309, { "id": [ 3, @@ -8908,11 +8925,20 @@ export default { ] } ], - "sendEmail": [ - 309, + "createCalendarEvent": [ + 301, { "input": [ - 487, + 488, + "CreateCalendarEventInput!" + ] + } + ], + "sendEmail": [ + 310, + { + "input": [ + 489, "SendEmailInput!" ] } @@ -8934,7 +8960,7 @@ export default { "String!" ], "connectionParameters": [ - 489, + 491, "EmailAccountConnectionParameters!" ], "id": [ @@ -8946,7 +8972,7 @@ export default { 171, { "input": [ - 491, + 493, "UpdateLabPublicFeatureFlagInput!" ] } @@ -8986,7 +9012,7 @@ export default { 78, { "input": [ - 492, + 494, "CreateOneAppTokenInput!" ] } @@ -9056,7 +9082,7 @@ export default { 267, { "file": [ - 358, + 359, "Upload!" ], "applicationUniversalIdentifier": [ @@ -9064,7 +9090,7 @@ export default { "String!" ], "fileFolder": [ - 494, + 496, "FileFolder!" ], "filePath": [ @@ -9173,7 +9199,7 @@ export default { 3 ], "update": [ - 357 + 358 ], "__typename": [ 1 @@ -9283,7 +9309,7 @@ export default { 3 ], "update": [ - 363 + 364 ], "__typename": [ 1 @@ -9448,17 +9474,17 @@ export default { 3 ], "viewFields": [ - 369 - ], - "viewFilters": [ 370 ], - "viewFilterGroups": [ + "viewFilters": [ 371 ], - "viewSorts": [ + "viewFilterGroups": [ 372 ], + "viewSorts": [ + 373 + ], "__typename": [ 1 ] @@ -9568,7 +9594,7 @@ export default { 3 ], "update": [ - 375 + 376 ], "__typename": [ 1 @@ -9606,7 +9632,7 @@ export default { 3 ], "update": [ - 379 + 380 ], "__typename": [ 1 @@ -9682,7 +9708,7 @@ export default { 3 ], "update": [ - 384 + 385 ], "__typename": [ 1 @@ -9746,10 +9772,10 @@ export default { 3 ], "groups": [ - 389 + 390 ], "fields": [ - 390 + 391 ], "__typename": [ 1 @@ -9769,7 +9795,7 @@ export default { 6 ], "fields": [ - 390 + 391 ], "__typename": [ 1 @@ -9937,7 +9963,7 @@ export default { 3 ], "tabs": [ - 402 + 403 ], "__typename": [ 1 @@ -9960,7 +9986,7 @@ export default { 89 ], "widgets": [ - 403 + 404 ], "__typename": [ 1 @@ -9983,7 +10009,7 @@ export default { 3 ], "gridPosition": [ - 404 + 405 ], "position": [ 15 @@ -10032,7 +10058,7 @@ export default { 3 ], "gridPosition": [ - 404 + 405 ], "position": [ 15 @@ -10058,7 +10084,7 @@ export default { 3 ], "gridPosition": [ - 404 + 405 ], "position": [ 15 @@ -10151,7 +10177,7 @@ export default { }, "CreateOneObjectInput": { "object": [ - 410 + 411 ], "__typename": [ 1 @@ -10211,7 +10237,7 @@ export default { }, "UpdateOneObjectInput": { "update": [ - 413 + 414 ], "id": [ 3 @@ -10266,7 +10292,7 @@ export default { }, "CreateOneIndexInput": { "index": [ - 415 + 416 ], "__typename": [ 1 @@ -10277,7 +10303,7 @@ export default { 3 ], "fields": [ - 416 + 417 ], "indexType": [ 48 @@ -10362,7 +10388,7 @@ export default { 3 ], "update": [ - 421 + 422 ], "__typename": [ 1 @@ -10522,7 +10548,7 @@ export default { 3 ], "update": [ - 426 + 427 ], "__typename": [ 1 @@ -10539,6 +10565,651 @@ export default { 1 ] }, + "ActivateWorkspaceInput": { + "displayName": [ + 1 + ], + "__typename": [ + 1 + ] + }, + "UpdateWorkspaceInput": { + "subdomain": [ + 1 + ], + "customDomain": [ + 1 + ], + "displayName": [ + 1 + ], + "logo": [ + 1 + ], + "inviteHash": [ + 1 + ], + "isPublicInviteLinkEnabled": [ + 6 + ], + "allowImpersonation": [ + 6 + ], + "isGoogleAuthEnabled": [ + 6 + ], + "isMicrosoftAuthEnabled": [ + 6 + ], + "isPasswordAuthEnabled": [ + 6 + ], + "isGoogleAuthBypassEnabled": [ + 6 + ], + "isMicrosoftAuthBypassEnabled": [ + 6 + ], + "isPasswordAuthBypassEnabled": [ + 6 + ], + "defaultRoleId": [ + 3 + ], + "isTwoFactorAuthenticationEnforced": [ + 6 + ], + "trashRetentionDays": [ + 11 + ], + "eventLogRetentionDays": [ + 11 + ], + "fastModel": [ + 1 + ], + "smartModel": [ + 1 + ], + "aiAdditionalInstructions": [ + 1 + ], + "editableProfileFields": [ + 1 + ], + "enabledAiModelIds": [ + 1 + ], + "useRecommendedModels": [ + 6 + ], + "isInternalMessagesImportEnabled": [ + 6 + ], + "__typename": [ + 1 + ] + }, + "CreateOneFieldMetadataInput": { + "field": [ + 431 + ], + "__typename": [ + 1 + ] + }, + "CreateFieldInput": { + "type": [ + 45 + ], + "name": [ + 1 + ], + "label": [ + 1 + ], + "description": [ + 1 + ], + "icon": [ + 1 + ], + "isActive": [ + 6 + ], + "isSystem": [ + 6 + ], + "isUIEditable": [ + 6 + ], + "isUIReadOnly": [ + 6 + ], + "isNullable": [ + 6 + ], + "isUnique": [ + 6 + ], + "defaultValue": [ + 15 + ], + "options": [ + 15 + ], + "settings": [ + 15 + ], + "objectMetadataId": [ + 3 + ], + "isLabelSyncedWithName": [ + 6 + ], + "isRemoteCreation": [ + 6 + ], + "relationCreationPayload": [ + 15 + ], + "morphRelationsCreationPayload": [ + 15 + ], + "__typename": [ + 1 + ] + }, + "UpdateOneFieldMetadataInput": { + "id": [ + 3 + ], + "update": [ + 433 + ], + "__typename": [ + 1 + ] + }, + "UpdateFieldInput": { + "universalIdentifier": [ + 1 + ], + "name": [ + 1 + ], + "label": [ + 1 + ], + "description": [ + 1 + ], + "icon": [ + 1 + ], + "isActive": [ + 6 + ], + "isSystem": [ + 6 + ], + "isUIEditable": [ + 6 + ], + "isUIReadOnly": [ + 6 + ], + "isNullable": [ + 6 + ], + "isUnique": [ + 6 + ], + "defaultValue": [ + 15 + ], + "options": [ + 15 + ], + "settings": [ + 15 + ], + "objectMetadataId": [ + 3 + ], + "isLabelSyncedWithName": [ + 6 + ], + "morphRelationsUpdatePayload": [ + 15 + ], + "__typename": [ + 1 + ] + }, + "DeleteOneFieldInput": { + "id": [ + 3 + ], + "__typename": [ + 1 + ] + }, + "CreateViewGroupInput": { + "id": [ + 3 + ], + "isVisible": [ + 6 + ], + "fieldValue": [ + 1 + ], + "position": [ + 11 + ], + "viewId": [ + 3 + ], + "__typename": [ + 1 + ] + }, + "UpdateViewGroupInput": { + "id": [ + 3 + ], + "update": [ + 437 + ], + "__typename": [ + 1 + ] + }, + "UpdateViewGroupInputUpdates": { + "fieldMetadataId": [ + 3 + ], + "isVisible": [ + 6 + ], + "fieldValue": [ + 1 + ], + "position": [ + 11 + ], + "__typename": [ + 1 + ] + }, + "DeleteViewGroupInput": { + "id": [ + 3 + ], + "__typename": [ + 1 + ] + }, + "DestroyViewGroupInput": { + "id": [ + 3 + ], + "__typename": [ + 1 + ] + }, + "WorkspaceMigrationInput": { + "actions": [ + 441 + ], + "__typename": [ + 1 + ] + }, + "WorkspaceMigrationDeleteActionInput": { + "type": [ + 442 + ], + "metadataName": [ + 334 + ], + "universalIdentifier": [ + 1 + ], + "__typename": [ + 1 + ] + }, + "WorkspaceMigrationActionType": {}, + "CreateRoleInput": { + "id": [ + 1 + ], + "label": [ + 1 + ], + "description": [ + 1 + ], + "icon": [ + 1 + ], + "canUpdateAllSettings": [ + 6 + ], + "canAccessAllTools": [ + 6 + ], + "canReadAllObjectRecords": [ + 6 + ], + "canUpdateAllObjectRecords": [ + 6 + ], + "canSoftDeleteAllObjectRecords": [ + 6 + ], + "canDestroyAllObjectRecords": [ + 6 + ], + "canBeAssignedToUsers": [ + 6 + ], + "canBeAssignedToAgents": [ + 6 + ], + "canBeAssignedToApiKeys": [ + 6 + ], + "__typename": [ + 1 + ] + }, + "UpdateRoleInput": { + "update": [ + 445 + ], + "id": [ + 3 + ], + "__typename": [ + 1 + ] + }, + "UpdateRolePayload": { + "label": [ + 1 + ], + "description": [ + 1 + ], + "icon": [ + 1 + ], + "canUpdateAllSettings": [ + 6 + ], + "canAccessAllTools": [ + 6 + ], + "canReadAllObjectRecords": [ + 6 + ], + "canUpdateAllObjectRecords": [ + 6 + ], + "canSoftDeleteAllObjectRecords": [ + 6 + ], + "canDestroyAllObjectRecords": [ + 6 + ], + "canBeAssignedToUsers": [ + 6 + ], + "canBeAssignedToAgents": [ + 6 + ], + "canBeAssignedToApiKeys": [ + 6 + ], + "__typename": [ + 1 + ] + }, + "UpsertObjectPermissionsInput": { + "roleId": [ + 3 + ], + "objectPermissions": [ + 447 + ], + "__typename": [ + 1 + ] + }, + "ObjectPermissionInput": { + "objectMetadataId": [ + 3 + ], + "canReadObjectRecords": [ + 6 + ], + "canUpdateObjectRecords": [ + 6 + ], + "canSoftDeleteObjectRecords": [ + 6 + ], + "canDestroyObjectRecords": [ + 6 + ], + "__typename": [ + 1 + ] + }, + "UpsertPermissionFlagsInput": { + "roleId": [ + 3 + ], + "permissionFlagKeys": [ + 1 + ], + "__typename": [ + 1 + ] + }, + "UpsertFieldPermissionsInput": { + "roleId": [ + 3 + ], + "fieldPermissions": [ + 450 + ], + "__typename": [ + 1 + ] + }, + "FieldPermissionInput": { + "objectMetadataId": [ + 3 + ], + "fieldMetadataId": [ + 3 + ], + "canReadFieldValue": [ + 6 + ], + "canUpdateFieldValue": [ + 6 + ], + "__typename": [ + 1 + ] + }, + "UpsertRowLevelPermissionPredicatesInput": { + "roleId": [ + 3 + ], + "objectMetadataId": [ + 3 + ], + "predicates": [ + 452 + ], + "predicateGroups": [ + 453 + ], + "__typename": [ + 1 + ] + }, + "RowLevelPermissionPredicateInput": { + "id": [ + 3 + ], + "fieldMetadataId": [ + 3 + ], + "operand": [ + 14 + ], + "value": [ + 15 + ], + "subFieldName": [ + 1 + ], + "workspaceMemberFieldMetadataId": [ + 1 + ], + "workspaceMemberSubFieldName": [ + 1 + ], + "rowLevelPermissionPredicateGroupId": [ + 3 + ], + "positionInRowLevelPermissionPredicateGroup": [ + 11 + ], + "__typename": [ + 1 + ] + }, + "RowLevelPermissionPredicateGroupInput": { + "id": [ + 3 + ], + "objectMetadataId": [ + 3 + ], + "parentRowLevelPermissionPredicateGroupId": [ + 3 + ], + "logicalOperator": [ + 12 + ], + "positionInRowLevelPermissionPredicateGroup": [ + 11 + ], + "__typename": [ + 1 + ] + }, + "CreateApplicationRegistrationInput": { + "name": [ + 1 + ], + "universalIdentifier": [ + 1 + ], + "oAuthRedirectUris": [ + 1 + ], + "oAuthScopes": [ + 1 + ], + "__typename": [ + 1 + ] + }, + "UpdateApplicationRegistrationInput": { + "id": [ + 1 + ], + "update": [ + 456 + ], + "__typename": [ + 1 + ] + }, + "UpdateApplicationRegistrationPayload": { + "name": [ + 1 + ], + "oAuthRedirectUris": [ + 1 + ], + "oAuthScopes": [ + 1 + ], + "isListed": [ + 6 + ], + "isPreInstalled": [ + 6 + ], + "__typename": [ + 1 + ] + }, + "CreateApplicationRegistrationVariableInput": { + "applicationRegistrationId": [ + 1 + ], + "key": [ + 1 + ], + "value": [ + 1 + ], + "description": [ + 1 + ], + "isSecret": [ + 6 + ], + "__typename": [ + 1 + ] + }, + "UpdateApplicationRegistrationVariableInput": { + "id": [ + 1 + ], + "update": [ + 459 + ], + "__typename": [ + 1 + ] + }, + "UpdateApplicationRegistrationVariablePayload": { + "value": [ + 1 + ], + "resetValue": [ + 6 + ], + "description": [ + 1 + ], + "__typename": [ + 1 + ] + }, "SendEmailViaDomainInput": { "emailingDomainId": [ 1 @@ -10627,7 +11298,7 @@ export default { 3 ], "update": [ - 432 + 465 ], "__typename": [ 1 @@ -10675,241 +11346,6 @@ export default { 1 ] }, - "CreateRoleInput": { - "id": [ - 1 - ], - "label": [ - 1 - ], - "description": [ - 1 - ], - "icon": [ - 1 - ], - "canUpdateAllSettings": [ - 6 - ], - "canAccessAllTools": [ - 6 - ], - "canReadAllObjectRecords": [ - 6 - ], - "canUpdateAllObjectRecords": [ - 6 - ], - "canSoftDeleteAllObjectRecords": [ - 6 - ], - "canDestroyAllObjectRecords": [ - 6 - ], - "canBeAssignedToUsers": [ - 6 - ], - "canBeAssignedToAgents": [ - 6 - ], - "canBeAssignedToApiKeys": [ - 6 - ], - "__typename": [ - 1 - ] - }, - "UpdateRoleInput": { - "update": [ - 437 - ], - "id": [ - 3 - ], - "__typename": [ - 1 - ] - }, - "UpdateRolePayload": { - "label": [ - 1 - ], - "description": [ - 1 - ], - "icon": [ - 1 - ], - "canUpdateAllSettings": [ - 6 - ], - "canAccessAllTools": [ - 6 - ], - "canReadAllObjectRecords": [ - 6 - ], - "canUpdateAllObjectRecords": [ - 6 - ], - "canSoftDeleteAllObjectRecords": [ - 6 - ], - "canDestroyAllObjectRecords": [ - 6 - ], - "canBeAssignedToUsers": [ - 6 - ], - "canBeAssignedToAgents": [ - 6 - ], - "canBeAssignedToApiKeys": [ - 6 - ], - "__typename": [ - 1 - ] - }, - "UpsertObjectPermissionsInput": { - "roleId": [ - 3 - ], - "objectPermissions": [ - 439 - ], - "__typename": [ - 1 - ] - }, - "ObjectPermissionInput": { - "objectMetadataId": [ - 3 - ], - "canReadObjectRecords": [ - 6 - ], - "canUpdateObjectRecords": [ - 6 - ], - "canSoftDeleteObjectRecords": [ - 6 - ], - "canDestroyObjectRecords": [ - 6 - ], - "__typename": [ - 1 - ] - }, - "UpsertPermissionFlagsInput": { - "roleId": [ - 3 - ], - "permissionFlagKeys": [ - 1 - ], - "__typename": [ - 1 - ] - }, - "UpsertFieldPermissionsInput": { - "roleId": [ - 3 - ], - "fieldPermissions": [ - 442 - ], - "__typename": [ - 1 - ] - }, - "FieldPermissionInput": { - "objectMetadataId": [ - 3 - ], - "fieldMetadataId": [ - 3 - ], - "canReadFieldValue": [ - 6 - ], - "canUpdateFieldValue": [ - 6 - ], - "__typename": [ - 1 - ] - }, - "UpsertRowLevelPermissionPredicatesInput": { - "roleId": [ - 3 - ], - "objectMetadataId": [ - 3 - ], - "predicates": [ - 444 - ], - "predicateGroups": [ - 445 - ], - "__typename": [ - 1 - ] - }, - "RowLevelPermissionPredicateInput": { - "id": [ - 3 - ], - "fieldMetadataId": [ - 3 - ], - "operand": [ - 14 - ], - "value": [ - 15 - ], - "subFieldName": [ - 1 - ], - "workspaceMemberFieldMetadataId": [ - 1 - ], - "workspaceMemberSubFieldName": [ - 1 - ], - "rowLevelPermissionPredicateGroupId": [ - 3 - ], - "positionInRowLevelPermissionPredicateGroup": [ - 11 - ], - "__typename": [ - 1 - ] - }, - "RowLevelPermissionPredicateGroupInput": { - "id": [ - 3 - ], - "objectMetadataId": [ - 3 - ], - "parentRowLevelPermissionPredicateGroupId": [ - 3 - ], - "logicalOperator": [ - 12 - ], - "positionInRowLevelPermissionPredicateGroup": [ - 11 - ], - "__typename": [ - 1 - ] - }, "RunAgentInput": { "agentUniversalIdentifier": [ 1 @@ -10946,7 +11382,7 @@ export default { 3 ], "update": [ - 449 + 471 ], "__typename": [ 1 @@ -10969,221 +11405,12 @@ export default { 1 ] }, - "CreateOneFieldMetadataInput": { - "field": [ - 451 - ], - "__typename": [ - 1 - ] - }, - "CreateFieldInput": { - "type": [ - 45 - ], - "name": [ - 1 - ], - "label": [ - 1 - ], - "description": [ - 1 - ], - "icon": [ - 1 - ], - "isActive": [ - 6 - ], - "isSystem": [ - 6 - ], - "isUIEditable": [ - 6 - ], - "isUIReadOnly": [ - 6 - ], - "isNullable": [ - 6 - ], - "isUnique": [ - 6 - ], - "defaultValue": [ - 15 - ], - "options": [ - 15 - ], - "settings": [ - 15 - ], - "objectMetadataId": [ - 3 - ], - "isLabelSyncedWithName": [ - 6 - ], - "isRemoteCreation": [ - 6 - ], - "relationCreationPayload": [ - 15 - ], - "morphRelationsCreationPayload": [ - 15 - ], - "__typename": [ - 1 - ] - }, - "UpdateOneFieldMetadataInput": { - "id": [ - 3 - ], - "update": [ - 453 - ], - "__typename": [ - 1 - ] - }, - "UpdateFieldInput": { - "universalIdentifier": [ - 1 - ], - "name": [ - 1 - ], - "label": [ - 1 - ], - "description": [ - 1 - ], - "icon": [ - 1 - ], - "isActive": [ - 6 - ], - "isSystem": [ - 6 - ], - "isUIEditable": [ - 6 - ], - "isUIReadOnly": [ - 6 - ], - "isNullable": [ - 6 - ], - "isUnique": [ - 6 - ], - "defaultValue": [ - 15 - ], - "options": [ - 15 - ], - "settings": [ - 15 - ], - "objectMetadataId": [ - 3 - ], - "isLabelSyncedWithName": [ - 6 - ], - "morphRelationsUpdatePayload": [ - 15 - ], - "__typename": [ - 1 - ] - }, - "DeleteOneFieldInput": { - "id": [ - 3 - ], - "__typename": [ - 1 - ] - }, - "CreateViewGroupInput": { - "id": [ - 3 - ], - "isVisible": [ - 6 - ], - "fieldValue": [ - 1 - ], - "position": [ - 11 - ], - "viewId": [ - 3 - ], - "__typename": [ - 1 - ] - }, - "UpdateViewGroupInput": { - "id": [ - 3 - ], - "update": [ - 457 - ], - "__typename": [ - 1 - ] - }, - "UpdateViewGroupInputUpdates": { - "fieldMetadataId": [ - 3 - ], - "isVisible": [ - 6 - ], - "fieldValue": [ - 1 - ], - "position": [ - 11 - ], - "__typename": [ - 1 - ] - }, - "DeleteViewGroupInput": { - "id": [ - 3 - ], - "__typename": [ - 1 - ] - }, - "DestroyViewGroupInput": { - "id": [ - 3 - ], - "__typename": [ - 1 - ] - }, "UpdateMessageFolderInput": { "id": [ 3 ], "update": [ - 461 + 473 ], "__typename": [ 1 @@ -11202,7 +11429,7 @@ export default { 3 ], "update": [ - 461 + 473 ], "__typename": [ 1 @@ -11213,7 +11440,7 @@ export default { 3 ], "update": [ - 464 + 476 ], "__typename": [ 1 @@ -11221,13 +11448,13 @@ export default { }, "UpdateCalendarChannelInputUpdates": { "visibility": [ - 328 + 329 ], "isContactAutoCreationEnabled": [ 6 ], "contactAutoCreationPolicy": [ - 329 + 330 ], "isSyncEnabled": [ 6 @@ -11318,99 +11545,6 @@ export default { 1 ] }, - "CreateApplicationRegistrationInput": { - "name": [ - 1 - ], - "universalIdentifier": [ - 1 - ], - "oAuthRedirectUris": [ - 1 - ], - "oAuthScopes": [ - 1 - ], - "__typename": [ - 1 - ] - }, - "UpdateApplicationRegistrationInput": { - "id": [ - 1 - ], - "update": [ - 472 - ], - "__typename": [ - 1 - ] - }, - "UpdateApplicationRegistrationPayload": { - "name": [ - 1 - ], - "oAuthRedirectUris": [ - 1 - ], - "oAuthScopes": [ - 1 - ], - "isListed": [ - 6 - ], - "isPreInstalled": [ - 6 - ], - "__typename": [ - 1 - ] - }, - "CreateApplicationRegistrationVariableInput": { - "applicationRegistrationId": [ - 1 - ], - "key": [ - 1 - ], - "value": [ - 1 - ], - "description": [ - 1 - ], - "isSecret": [ - 6 - ], - "__typename": [ - 1 - ] - }, - "UpdateApplicationRegistrationVariableInput": { - "id": [ - 1 - ], - "update": [ - 475 - ], - "__typename": [ - 1 - ] - }, - "UpdateApplicationRegistrationVariablePayload": { - "value": [ - 1 - ], - "resetValue": [ - 6 - ], - "description": [ - 1 - ], - "__typename": [ - 1 - ] - }, "UpdateWorkspaceMemberSettingsInput": { "workspaceMemberId": [ 3 @@ -11422,114 +11556,6 @@ export default { 1 ] }, - "ActivateWorkspaceInput": { - "displayName": [ - 1 - ], - "__typename": [ - 1 - ] - }, - "UpdateWorkspaceInput": { - "subdomain": [ - 1 - ], - "customDomain": [ - 1 - ], - "displayName": [ - 1 - ], - "logo": [ - 1 - ], - "inviteHash": [ - 1 - ], - "isPublicInviteLinkEnabled": [ - 6 - ], - "allowImpersonation": [ - 6 - ], - "isGoogleAuthEnabled": [ - 6 - ], - "isMicrosoftAuthEnabled": [ - 6 - ], - "isPasswordAuthEnabled": [ - 6 - ], - "isGoogleAuthBypassEnabled": [ - 6 - ], - "isMicrosoftAuthBypassEnabled": [ - 6 - ], - "isPasswordAuthBypassEnabled": [ - 6 - ], - "defaultRoleId": [ - 3 - ], - "isTwoFactorAuthenticationEnforced": [ - 6 - ], - "trashRetentionDays": [ - 11 - ], - "eventLogRetentionDays": [ - 11 - ], - "fastModel": [ - 1 - ], - "smartModel": [ - 1 - ], - "aiAdditionalInstructions": [ - 1 - ], - "editableProfileFields": [ - 1 - ], - "enabledAiModelIds": [ - 1 - ], - "useRecommendedModels": [ - 6 - ], - "isInternalMessagesImportEnabled": [ - 6 - ], - "__typename": [ - 1 - ] - }, - "WorkspaceMigrationInput": { - "actions": [ - 480 - ], - "__typename": [ - 1 - ] - }, - "WorkspaceMigrationDeleteActionInput": { - "type": [ - 481 - ], - "metadataName": [ - 333 - ], - "universalIdentifier": [ - 1 - ], - "__typename": [ - 1 - ] - }, - "WorkspaceMigrationActionType": {}, "SetupOIDCSsoInput": { "name": [ 1 @@ -11590,6 +11616,44 @@ export default { ] }, "AnalyticsType": {}, + "CreateCalendarEventInput": { + "connectedAccountId": [ + 1 + ], + "title": [ + 1 + ], + "description": [ + 1 + ], + "location": [ + 1 + ], + "startsAt": [ + 1 + ], + "endsAt": [ + 1 + ], + "isFullDay": [ + 6 + ], + "timeZone": [ + 1 + ], + "attendees": [ + 1 + ], + "sendInvitations": [ + 6 + ], + "addConferencing": [ + 6 + ], + "__typename": [ + 1 + ] + }, "SendEmailInput": { "connectedAccountId": [ 1 @@ -11613,7 +11677,7 @@ export default { 1 ], "files": [ - 488 + 490 ], "__typename": [ 1 @@ -11632,13 +11696,13 @@ export default { }, "EmailAccountConnectionParameters": { "IMAP": [ - 490 + 492 ], "SMTP": [ - 490 + 492 ], "CALDAV": [ - 490 + 492 ], "__typename": [ 1 @@ -11677,7 +11741,7 @@ export default { }, "CreateOneAppTokenInput": { "appToken": [ - 493 + 495 ], "__typename": [ 1 @@ -11706,13 +11770,13 @@ export default { 235, { "input": [ - 496, + 498, "LogicFunctionLogsInput!" ] } ], "onAgentChatEvent": [ - 321, + 322, { "threadId": [ 3, @@ -11721,10 +11785,10 @@ export default { } ], "eventLogsLive": [ - 311, + 312, { "table": [ - 344, + 345, "EventLogTable!" ] } diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index a27fa18f30..d646d6f8d5 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -1060,6 +1060,28 @@ export type CreateApprovedAccessDomainInput = { email: Scalars['String']['input']; }; +export type CreateCalendarEventInput = { + addConferencing?: InputMaybe; + attendees?: InputMaybe; + connectedAccountId: Scalars['String']['input']; + description?: InputMaybe; + endsAt: Scalars['String']['input']; + isFullDay?: InputMaybe; + location?: InputMaybe; + sendInvitations?: InputMaybe; + startsAt: Scalars['String']['input']; + timeZone?: InputMaybe; + title: Scalars['String']['input']; +}; + +export type CreateCalendarEventOutput = { + __typename?: 'CreateCalendarEventOutput'; + conferenceLink?: Maybe; + error?: Maybe; + iCalUid?: Maybe; + success: Scalars['Boolean']['output']; +}; + export type CreateCommandMenuItemInput = { availabilityObjectMetadataId?: InputMaybe; availabilityType?: InputMaybe; @@ -2469,6 +2491,7 @@ export type Mutation = { createApplicationRegistrationVariable: ApplicationRegistrationVariable; createApprovedAccessDomain: ApprovedAccessDomain; createBillingPaymentMethodSetupIntent: BillingPaymentIntent; + createCalendarEvent: CreateCalendarEventOutput; createChatThread: AgentChatThread; createCommandMenuItem: CommandMenuItem; createDevelopmentApplication: DevelopmentApplication; @@ -2749,6 +2772,11 @@ export type MutationCreateApprovedAccessDomainArgs = { }; +export type MutationCreateCalendarEventArgs = { + input: CreateCalendarEventInput; +}; + + export type MutationCreateCommandMenuItemArgs = { input: CreateCommandMenuItemInput; }; @@ -4113,6 +4141,7 @@ export enum PermissionFlagType { BILLING = 'BILLING', CODE_INTERPRETER_TOOL = 'CODE_INTERPRETER_TOOL', CONNECTED_ACCOUNTS = 'CONNECTED_ACCOUNTS', + CREATE_CALENDAR_EVENT_TOOL = 'CREATE_CALENDAR_EVENT_TOOL', DATA_MODEL = 'DATA_MODEL', DOWNLOAD_FILE = 'DOWNLOAD_FILE', EXPORT_CSV = 'EXPORT_CSV', diff --git a/packages/twenty-front/src/generated/graphql.ts b/packages/twenty-front/src/generated/graphql.ts index 55c6c1ed19..3c1a5a5455 100644 --- a/packages/twenty-front/src/generated/graphql.ts +++ b/packages/twenty-front/src/generated/graphql.ts @@ -544,6 +544,7 @@ export type WorkflowAction = { export enum WorkflowActionType { AI_AGENT = 'AI_AGENT', CODE = 'CODE', + CREATE_CALENDAR_EVENT = 'CREATE_CALENDAR_EVENT', CREATE_RECORD = 'CREATE_RECORD', DELAY = 'DELAY', DELETE_RECORD = 'DELETE_RECORD', diff --git a/packages/twenty-front/src/modules/accounts/constants/GoogleCalendarEventsScope.ts b/packages/twenty-front/src/modules/accounts/constants/GoogleCalendarEventsScope.ts new file mode 100644 index 0000000000..2ca2ea1498 --- /dev/null +++ b/packages/twenty-front/src/modules/accounts/constants/GoogleCalendarEventsScope.ts @@ -0,0 +1,2 @@ +export const GOOGLE_CALENDAR_EVENTS_SCOPE = + 'https://www.googleapis.com/auth/calendar.events'; diff --git a/packages/twenty-front/src/modules/accounts/constants/MicrosoftCalendarsReadWriteScope.ts b/packages/twenty-front/src/modules/accounts/constants/MicrosoftCalendarsReadWriteScope.ts new file mode 100644 index 0000000000..3306f1f4ad --- /dev/null +++ b/packages/twenty-front/src/modules/accounts/constants/MicrosoftCalendarsReadWriteScope.ts @@ -0,0 +1 @@ +export const MICROSOFT_CALENDARS_READ_WRITE_SCOPE = 'Calendars.ReadWrite'; diff --git a/packages/twenty-front/src/modules/accounts/utils/hasMissingCreateCalendarEventScopes.ts b/packages/twenty-front/src/modules/accounts/utils/hasMissingCreateCalendarEventScopes.ts new file mode 100644 index 0000000000..091e6a0eb9 --- /dev/null +++ b/packages/twenty-front/src/modules/accounts/utils/hasMissingCreateCalendarEventScopes.ts @@ -0,0 +1,39 @@ +import { GOOGLE_CALENDAR_EVENTS_SCOPE } from '@/accounts/constants/GoogleCalendarEventsScope'; +import { MICROSOFT_CALENDARS_READ_WRITE_SCOPE } from '@/accounts/constants/MicrosoftCalendarsReadWriteScope'; +import { type ConnectedAccount } from '@/accounts/types/ConnectedAccount'; +import { ConnectedAccountProvider } from 'twenty-shared/types'; +import { assertUnreachable, isDefined } from 'twenty-shared/utils'; + +export const getMissingCreateCalendarEventScopes = ( + connectedAccount: Pick, +): string[] => { + const scopes = connectedAccount.scopes; + + switch (connectedAccount.provider) { + case ConnectedAccountProvider.GOOGLE: { + const hasScope = + isDefined(scopes) && + scopes.some((scope) => scope === GOOGLE_CALENDAR_EVENTS_SCOPE); + + return hasScope ? [] : [GOOGLE_CALENDAR_EVENTS_SCOPE]; + } + case ConnectedAccountProvider.MICROSOFT: { + const hasScope = + isDefined(scopes) && + scopes.some((scope) => scope === MICROSOFT_CALENDARS_READ_WRITE_SCOPE); + + return hasScope ? [] : [MICROSOFT_CALENDARS_READ_WRITE_SCOPE]; + } + case ConnectedAccountProvider.IMAP_SMTP_CALDAV: + case ConnectedAccountProvider.OIDC: + case ConnectedAccountProvider.SAML: + case ConnectedAccountProvider.EMAIL_GROUP: + case ConnectedAccountProvider.APP: + return []; + default: + assertUnreachable( + connectedAccount.provider, + 'Provider not yet supported for create calendar event actions', + ); + } +}; diff --git a/packages/twenty-front/src/modules/settings/roles/role-permissions/permission-flags/hooks/useActionRolePermissionFlagConfig.ts b/packages/twenty-front/src/modules/settings/roles/role-permissions/permission-flags/hooks/useActionRolePermissionFlagConfig.ts index 3c6b1f2a8b..938f5ec829 100644 --- a/packages/twenty-front/src/modules/settings/roles/role-permissions/permission-flags/hooks/useActionRolePermissionFlagConfig.ts +++ b/packages/twenty-front/src/modules/settings/roles/role-permissions/permission-flags/hooks/useActionRolePermissionFlagConfig.ts @@ -4,6 +4,7 @@ import { useMemo } from 'react'; import { IconApi, IconAt, + IconCalendarEvent, IconCode, IconDownload, IconFileExport, @@ -77,6 +78,16 @@ export const useActionRolePermissionFlagConfig = ({ isRelevantForApiKeys: true, isRelevantForUsers: true, }, + { + key: PermissionFlagType.CREATE_CALENDAR_EVENT_TOOL, + name: t`Create Calendar Event`, + description: t`Create calendar events via connected accounts`, + Icon: IconCalendarEvent, + isToolPermission: true, + isRelevantForAgents: true, + isRelevantForApiKeys: true, + isRelevantForUsers: true, + }, { key: PermissionFlagType.HTTP_REQUEST_TOOL, name: t`HTTP Request`, diff --git a/packages/twenty-front/src/modules/workflow/types/CalendarEventFormData.ts b/packages/twenty-front/src/modules/workflow/types/CalendarEventFormData.ts new file mode 100644 index 0000000000..d004576856 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/types/CalendarEventFormData.ts @@ -0,0 +1,13 @@ +export type CalendarEventFormData = { + connectedAccountId: string; + title: string; + description: string; + location: string; + startsAt: string; + endsAt: string; + isFullDay: boolean; + timeZone: string; + attendees: string; + sendInvitations: boolean; + addConferencing: boolean; +}; diff --git a/packages/twenty-front/src/modules/workflow/types/Workflow.ts b/packages/twenty-front/src/modules/workflow/types/Workflow.ts index f69a6a9b94..38e9b56f82 100644 --- a/packages/twenty-front/src/modules/workflow/types/Workflow.ts +++ b/packages/twenty-front/src/modules/workflow/types/Workflow.ts @@ -4,6 +4,7 @@ import { type SingleRecordAvailability, type workflowAiAgentActionSchema, type workflowCodeActionSchema, + type workflowCreateCalendarEventActionSchema, type workflowCreateRecordActionSchema, type workflowCronTriggerSchema, type workflowDatabaseEventTriggerSchema, @@ -42,6 +43,9 @@ export type WorkflowSendEmailAction = z.infer< export type WorkflowDraftEmailAction = z.infer< typeof workflowDraftEmailActionSchema >; +export type WorkflowCreateCalendarEventAction = z.infer< + typeof workflowCreateCalendarEventActionSchema +>; export type WorkflowCreateRecordAction = z.infer< typeof workflowCreateRecordActionSchema >; @@ -78,6 +82,7 @@ export type WorkflowAction = | WorkflowLogicFunctionAction | WorkflowSendEmailAction | WorkflowDraftEmailAction + | WorkflowCreateCalendarEventAction | WorkflowCreateRecordAction | WorkflowUpdateRecordAction | WorkflowDeleteRecordAction diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramStepNodeIcon.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramStepNodeIcon.tsx index a6e86b24e0..47b7b67a70 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramStepNodeIcon.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramStepNodeIcon.tsx @@ -35,7 +35,8 @@ export const WorkflowDiagramStepNodeIcon = ({ case 'CODE': case 'HTTP_REQUEST': case 'SEND_EMAIL': - case 'DRAFT_EMAIL': { + case 'DRAFT_EMAIL': + case 'CREATE_CALENDAR_EVENT': { return ( ); } + case 'CREATE_CALENDAR_EVENT': { + return ( + + ); + } case 'CREATE_RECORD': { return ( ); } + case 'CREATE_CALENDAR_EVENT': { + return ( + + ); + } case 'CREATE_RECORD': { return ( void; + }; +}; + +export const WorkflowEditActionCreateCalendarEvent = ({ + action, + actionOptions, +}: WorkflowEditActionCreateCalendarEventProps) => { + const { formData, handleFieldChange, saveAction } = useCalendarEventForm({ + action, + onActionUpdate: + actionOptions.readonly === true + ? undefined + : actionOptions.onActionUpdate, + readonly: actionOptions.readonly === true, + }); + + const navigate = useNavigateSettings(); + const { closeSidePanelMenu } = useSidePanelMenu(); + const { accounts: myAccounts, loading } = useMyConnectedAccounts(); + const { triggerApisOAuth } = useTriggerApisOAuth(); + + const workflowVisualizerWorkflowId = useAtomComponentStateValue( + workflowVisualizerWorkflowIdComponentState, + ); + const redirectUrl = `/object/workflow/${workflowVisualizerWorkflowId}`; + + const connectedAccountOptions: SelectOption[] = myAccounts + .filter((account) => { + if (account.provider === ConnectedAccountProvider.IMAP_SMTP_CALDAV) { + return isDefined(account.connectionParameters?.CALDAV); + } + + return CALENDAR_CAPABLE_PROVIDERS.includes(account.provider); + }) + .map((account) => ({ label: account.handle, value: account.id })); + + const selectedAccount = myAccounts.find( + (account) => account.id === formData.connectedAccountId, + ); + + const missingScopes = + isDefined(selectedAccount) && + getMissingCreateCalendarEventScopes(selectedAccount).length > 0 + ? { + provider: selectedAccount.provider, + loginHint: selectedAccount.handle, + } + : null; + + const handleReauthorize = async () => { + if (!isDefined(missingScopes)) { + return; + } + + await triggerApisOAuth(missingScopes.provider, { + redirectLocation: redirectUrl, + loginHint: missingScopes.loginHint, + }); + }; + + useEffect(() => { + return () => { + saveAction.flush(); + }; + }, [saveAction]); + + if (loading) { + return null; + } + + return ( + <> + + + handleFieldChange('connectedAccountId', value ?? '') + } + readonly={actionOptions.readonly} + callToActionButton={{ + onClick: () => { + closeSidePanelMenu(); + navigate(SettingsPath.NewAccount); + }, + Icon: IconPlus, + text: t`Add account`, + }} + /> + {isDefined(missingScopes) && ( + + )} + handleFieldChange('title', value)} + VariablePicker={WorkflowVariablePicker} + /> + handleFieldChange('description', value)} + VariablePicker={WorkflowVariablePicker} + /> + handleFieldChange('location', value)} + VariablePicker={WorkflowVariablePicker} + /> + handleFieldChange('startsAt', value ?? '')} + VariablePicker={WorkflowVariablePicker} + /> + handleFieldChange('endsAt', value ?? '')} + VariablePicker={WorkflowVariablePicker} + /> + []} + onChange={(value) => handleFieldChange('timeZone', value ?? '')} + readonly={actionOptions.readonly} + VariablePicker={WorkflowVariablePicker} + /> + handleFieldChange('isFullDay', value)} + disabled={actionOptions.readonly} + /> + handleFieldChange('attendees', value)} + VariablePicker={WorkflowVariablePicker} + /> + handleFieldChange('sendInvitations', value)} + disabled={actionOptions.readonly} + /> + handleFieldChange('addConferencing', value)} + disabled={actionOptions.readonly} + /> + + {!actionOptions.readonly && } + + ); +}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/constants/CoreActions.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/constants/CoreActions.ts index e5be93bf42..b37f74f875 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/constants/CoreActions.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/constants/CoreActions.ts @@ -1,5 +1,6 @@ import { type WorkflowActionType } from '@/workflow/types/Workflow'; import { CODE_ACTION } from '@/workflow/workflow-steps/workflow-actions/constants/actions/CodeAction'; +import { CREATE_CALENDAR_EVENT_ACTION } from '@/workflow/workflow-steps/workflow-actions/constants/actions/CreateCalendarEventAction'; import { DRAFT_EMAIL_ACTION } from '@/workflow/workflow-steps/workflow-actions/constants/actions/DraftEmailAction'; import { HTTP_REQUEST_ACTION } from '@/workflow/workflow-steps/workflow-actions/constants/actions/HttpRequestAction'; import { SEND_EMAIL_ACTION } from '@/workflow/workflow-steps/workflow-actions/constants/actions/SendEmailAction'; @@ -8,7 +9,17 @@ export const CORE_ACTIONS: Array<{ defaultLabel: string; type: Extract< WorkflowActionType, - 'CODE' | 'SEND_EMAIL' | 'DRAFT_EMAIL' | 'HTTP_REQUEST' + | 'CODE' + | 'SEND_EMAIL' + | 'DRAFT_EMAIL' + | 'HTTP_REQUEST' + | 'CREATE_CALENDAR_EVENT' >; icon: string; -}> = [SEND_EMAIL_ACTION, DRAFT_EMAIL_ACTION, CODE_ACTION, HTTP_REQUEST_ACTION]; +}> = [ + SEND_EMAIL_ACTION, + DRAFT_EMAIL_ACTION, + CREATE_CALENDAR_EVENT_ACTION, + CODE_ACTION, + HTTP_REQUEST_ACTION, +]; diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/constants/actions/CreateCalendarEventAction.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/constants/actions/CreateCalendarEventAction.ts new file mode 100644 index 0000000000..aaabc37f5d --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/constants/actions/CreateCalendarEventAction.ts @@ -0,0 +1,11 @@ +import { type WorkflowActionType } from '@/workflow/types/Workflow'; + +export const CREATE_CALENDAR_EVENT_ACTION: { + defaultLabel: string; + type: Extract; + icon: string; +} = { + defaultLabel: 'Create Calendar Event', + type: 'CREATE_CALENDAR_EVENT', + icon: 'IconCalendarEvent', +}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/hooks/useCalendarEventForm.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/hooks/useCalendarEventForm.ts new file mode 100644 index 0000000000..ad6f1f7b06 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/hooks/useCalendarEventForm.ts @@ -0,0 +1,71 @@ +import { type CalendarEventFormData } from '@/workflow/types/CalendarEventFormData'; +import { type WorkflowCreateCalendarEventAction } from '@/workflow/types/Workflow'; +import { useState } from 'react'; +import { type JsonValue } from 'type-fest'; +import { useDebouncedCallback } from 'use-debounce'; + +type UseCalendarEventFormParams = { + action: WorkflowCreateCalendarEventAction; + onActionUpdate?: (action: WorkflowCreateCalendarEventAction) => void; + readonly: boolean; +}; + +export const useCalendarEventForm = ({ + action, + onActionUpdate, + readonly, +}: UseCalendarEventFormParams) => { + const [formData, setFormData] = useState(() => { + const input = action.settings.input; + + return { + connectedAccountId: input.connectedAccountId, + title: input.title, + description: input.description ?? '', + location: input.location ?? '', + startsAt: input.startsAt, + endsAt: input.endsAt, + isFullDay: input.isFullDay, + timeZone: input.timeZone ?? '', + attendees: input.attendees ?? '', + sendInvitations: input.sendInvitations, + addConferencing: input.addConferencing, + }; + }); + + const saveAction = useDebouncedCallback( + (nextFormData: CalendarEventFormData) => { + if (readonly) { + return; + } + + onActionUpdate?.({ + ...action, + settings: { + ...action.settings, + input: { ...nextFormData }, + }, + }); + }, + 1_000, + ); + + const handleFieldChange = ( + fieldName: keyof CalendarEventFormData, + updatedValue: JsonValue, + ) => { + const newFormData: CalendarEventFormData = { + ...formData, + [fieldName]: updatedValue, + }; + + setFormData(newFormData); + saveAction(newFormData); + }; + + return { + formData, + handleFieldChange, + saveAction, + }; +}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/utils/getActionIcon.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/utils/getActionIcon.ts index 6a5c3f073e..6e825d99af 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/utils/getActionIcon.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/utils/getActionIcon.ts @@ -20,6 +20,7 @@ export const getActionIcon = (actionType: WorkflowActionType) => { case 'HTTP_REQUEST': case 'SEND_EMAIL': case 'DRAFT_EMAIL': + case 'CREATE_CALENDAR_EVENT': return CORE_ACTIONS.find((item) => item.type === actionType)?.icon; case 'LOGIC_FUNCTION': return 'IconFunction'; diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/utils/getActionIconColorOrThrow.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/utils/getActionIconColorOrThrow.ts index 26f368aaf5..dcfea980e1 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/utils/getActionIconColorOrThrow.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/utils/getActionIconColorOrThrow.ts @@ -11,6 +11,7 @@ export const getActionIconColorOrThrow = ( case 'HTTP_REQUEST': case 'SEND_EMAIL': case 'DRAFT_EMAIL': + case 'CREATE_CALENDAR_EVENT': return themeCssVariables.color.red; case 'CREATE_RECORD': case 'UPDATE_RECORD': diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/generate/computeStepOutputSchema.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/generate/computeStepOutputSchema.ts index b35a9c9480..df97d16c05 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/generate/computeStepOutputSchema.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/generate/computeStepOutputSchema.ts @@ -230,6 +230,35 @@ export const computeStepOutputSchema = ({ }; } + case 'CREATE_CALENDAR_EVENT': { + return { + success: { + isLeaf: true, + type: FieldMetadataType.BOOLEAN, + label: 'Success', + value: true, + }, + iCalUid: { + isLeaf: true, + type: FieldMetadataType.TEXT, + label: 'iCal UID', + value: '', + }, + externalEventId: { + isLeaf: true, + type: FieldMetadataType.TEXT, + label: 'External Event ID', + value: '', + }, + conferenceLink: { + isLeaf: true, + type: FieldMetadataType.TEXT, + label: 'Conference Link', + value: '', + }, + }; + } + case 'FILTER': case 'DELAY': case 'EMPTY': { diff --git a/packages/twenty-server/package.json b/packages/twenty-server/package.json index 29b3fba641..ce5db06fb9 100644 --- a/packages/twenty-server/package.json +++ b/packages/twenty-server/package.json @@ -113,6 +113,7 @@ "graphql-upload": "16.0.2", "graphql-yoga": "4.0.5", "html-to-text": "^9.0.5", + "ical-generator": "^11.0.0", "imapflow": "1.4.2", "ioredis": "5.10.1", "jsdom": "^29.1.1", diff --git a/packages/twenty-server/src/engine/core-modules/auth/utils/get-microsoft-apis-oauth-scopes.ts b/packages/twenty-server/src/engine/core-modules/auth/utils/get-microsoft-apis-oauth-scopes.ts index 632cb0b7f9..54088b1ea6 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/utils/get-microsoft-apis-oauth-scopes.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/utils/get-microsoft-apis-oauth-scopes.ts @@ -6,7 +6,7 @@ export const getMicrosoftApisOauthScopes = () => { 'offline_access', 'Mail.ReadWrite', 'Mail.Send', - 'Calendars.Read', + 'Calendars.ReadWrite', 'User.Read', ]; diff --git a/packages/twenty-server/src/engine/core-modules/core-engine.module.ts b/packages/twenty-server/src/engine/core-modules/core-engine.module.ts index 786aaec3c3..9781a17778 100644 --- a/packages/twenty-server/src/engine/core-modules/core-engine.module.ts +++ b/packages/twenty-server/src/engine/core-modules/core-engine.module.ts @@ -74,6 +74,7 @@ import { CodeInterpreterSessionCleanupModule } from 'src/engine/core-modules/cod import { TrashCleanupModule } from 'src/engine/trash-cleanup/trash-cleanup.module'; import { WorkspaceEventEmitterModule } from 'src/engine/workspace-event-emitter/workspace-event-emitter.module'; import { ChannelSyncModule } from 'src/modules/connected-account/channel-sync/channel-sync.module'; +import { CreateCalendarEventModule } from 'src/modules/calendar/calendar-event-creation-manager/create-calendar-event.module'; import { DashboardModule } from 'src/modules/dashboard/dashboard.module'; import { SendEmailModule } from 'src/modules/messaging/message-outbound-manager/send-email.module'; import { ClientConfigModule } from './client-config/client-config.module'; @@ -130,6 +131,7 @@ import { FileModule } from './file/file.module'; ImapSmtpCaldavModule, ChannelSyncModule, SendEmailModule, + CreateCalendarEventModule, FileStorageModule.forRoot(), LoggerModule.forRootAsync({ useFactory: loggerModuleFactory, diff --git a/packages/twenty-server/src/engine/core-modules/tool-provider/constants/action-tool-label.constant.ts b/packages/twenty-server/src/engine/core-modules/tool-provider/constants/action-tool-label.constant.ts index 0bed0a1502..f094a09aff 100644 --- a/packages/twenty-server/src/engine/core-modules/tool-provider/constants/action-tool-label.constant.ts +++ b/packages/twenty-server/src/engine/core-modules/tool-provider/constants/action-tool-label.constant.ts @@ -7,6 +7,7 @@ export const ACTION_TOOL_IDS = [ 'http_request', 'send_email', 'draft_email', + 'create_calendar_event', 'search_help_center', 'code_interpreter', 'navigate_app', @@ -24,6 +25,9 @@ export const ACTION_TOOL_LABELS: Record = { draft_email: { label: i18nLabel(msg`Draft Email`), }, + create_calendar_event: { + label: i18nLabel(msg`Create Calendar Event`), + }, search_help_center: { label: i18nLabel(msg`Search Help Center`), }, diff --git a/packages/twenty-server/src/engine/core-modules/tool-provider/providers/action-tool.provider.ts b/packages/twenty-server/src/engine/core-modules/tool-provider/providers/action-tool.provider.ts index 521d090e6e..06639b45cd 100644 --- a/packages/twenty-server/src/engine/core-modules/tool-provider/providers/action-tool.provider.ts +++ b/packages/twenty-server/src/engine/core-modules/tool-provider/providers/action-tool.provider.ts @@ -21,6 +21,7 @@ import { toToolJsonSchema } from 'src/engine/core-modules/record-crud/utils/to-t import { type ToolDescriptor } from 'src/engine/core-modules/tool-provider/types/tool-descriptor.type'; import { type ToolIndexEntry } from 'src/engine/core-modules/tool-provider/types/tool-index-entry.type'; import { CodeInterpreterService } from 'src/engine/core-modules/code-interpreter/code-interpreter.service'; +import { CreateCalendarEventTool } from 'src/engine/core-modules/tool/tools/calendar-tool/create-calendar-event-tool'; import { CodeInterpreterTool } from 'src/engine/core-modules/tool/tools/code-interpreter-tool/code-interpreter-tool'; import { DraftEmailTool } from 'src/engine/core-modules/tool/tools/email-tool/draft-email-tool'; import { SendEmailTool } from 'src/engine/core-modules/tool/tools/email-tool/send-email-tool'; @@ -43,6 +44,7 @@ export class ActionToolProvider implements ToolProvider { private readonly httpTool: HttpTool, private readonly sendEmailTool: SendEmailTool, private readonly draftEmailTool: DraftEmailTool, + private readonly createCalendarEventTool: CreateCalendarEventTool, private readonly searchHelpCenterTool: SearchHelpCenterTool, private readonly codeInterpreterTool: CodeInterpreterTool, private readonly navigateAppTool: NavigateAppTool, @@ -56,6 +58,7 @@ export class ActionToolProvider implements ToolProvider { ['http_request', this.httpTool], ['send_email', this.sendEmailTool], ['draft_email', this.draftEmailTool], + ['create_calendar_event', this.createCalendarEventTool], ['search_help_center', this.searchHelpCenterTool], ['code_interpreter', this.codeInterpreterTool], ['navigate_app', this.navigateAppTool], @@ -117,6 +120,24 @@ export class ActionToolProvider implements ToolProvider { ); } + const hasCreateCalendarEventPermission = + await this.permissionsService.hasToolPermission( + context.rolePermissionConfig, + context.workspaceId, + PermissionFlagType.CREATE_CALENDAR_EVENT_TOOL, + ); + + if (hasCreateCalendarEventPermission) { + descriptors.push( + this.buildDescriptor( + 'create_calendar_event', + this.createCalendarEventTool, + includeSchemas, + context.locale, + ), + ); + } + descriptors.push( this.buildDescriptor( 'search_help_center', diff --git a/packages/twenty-server/src/engine/core-modules/tool/tool.module.ts b/packages/twenty-server/src/engine/core-modules/tool/tool.module.ts index 287123fa8a..fe1e93a403 100644 --- a/packages/twenty-server/src/engine/core-modules/tool/tool.module.ts +++ b/packages/twenty-server/src/engine/core-modules/tool/tool.module.ts @@ -7,6 +7,7 @@ import { FileEntity } from 'src/engine/core-modules/file/entities/file.entity'; import { FileModule } from 'src/engine/core-modules/file/file.module'; import { JwtModule } from 'src/engine/core-modules/jwt/jwt.module'; import { SecureHttpClientModule } from 'src/engine/core-modules/secure-http-client/secure-http-client.module'; +import { CreateCalendarEventTool } from 'src/engine/core-modules/tool/tools/calendar-tool/create-calendar-event-tool'; import { CodeInterpreterTool } from 'src/engine/core-modules/tool/tools/code-interpreter-tool/code-interpreter-tool'; import { DraftEmailTool } from 'src/engine/core-modules/tool/tools/email-tool/draft-email-tool'; import { EmailComposerService } from 'src/engine/core-modules/tool/tools/email-tool/email-composer.service'; @@ -22,6 +23,7 @@ import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-ac import { NavigationMenuItemModule } from 'src/engine/metadata-modules/navigation-menu-item/navigation-menu-item.module'; import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module'; import { ViewModule } from 'src/engine/metadata-modules/view/view.module'; +import { CalendarEventCreationManagerModule } from 'src/modules/calendar/calendar-event-creation-manager/calendar-event-creation-manager.module'; import { MessagingImportManagerModule } from 'src/modules/messaging/message-import-manager/messaging-import-manager.module'; import { MessagingSendManagerModule } from 'src/modules/messaging/message-outbound-manager/messaging-send-manager.module'; import { provideWorkspaceScopedRepository } from 'src/engine/twenty-orm/workspace-scoped-repository/provide-workspace-scoped-repository'; @@ -29,6 +31,7 @@ import { provideWorkspaceScopedRepository } from 'src/engine/twenty-orm/workspac imports: [ MessagingImportManagerModule, MessagingSendManagerModule, + CalendarEventCreationManagerModule, TypeOrmModule.forFeature([FileEntity, ConnectedAccountEntity]), ApplicationModule, FeatureFlagModule, @@ -44,6 +47,7 @@ import { provideWorkspaceScopedRepository } from 'src/engine/twenty-orm/workspac HttpTool, SendEmailTool, DraftEmailTool, + CreateCalendarEventTool, EmailComposerService, SearchHelpCenterTool, CodeInterpreterTool, @@ -57,6 +61,7 @@ import { provideWorkspaceScopedRepository } from 'src/engine/twenty-orm/workspac HttpTool, SendEmailTool, DraftEmailTool, + CreateCalendarEventTool, EmailComposerService, SearchHelpCenterTool, CodeInterpreterTool, diff --git a/packages/twenty-server/src/engine/core-modules/tool/tools/calendar-tool/calendar-tool.schema.ts b/packages/twenty-server/src/engine/core-modules/tool/tools/calendar-tool/calendar-tool.schema.ts new file mode 100644 index 0000000000..2306e1efc4 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/tool/tools/calendar-tool/calendar-tool.schema.ts @@ -0,0 +1,60 @@ +import { isValidUuid } from 'twenty-shared/utils'; +import { z } from 'zod'; + +export const CreateCalendarEventToolInputZodSchema = z.object({ + title: z.string().describe('The title of the calendar event'), + description: z + .string() + .describe('The event description or agenda') + .optional(), + location: z + .string() + .describe('The physical or virtual location of the event') + .optional(), + startsAt: z + .string() + .describe( + 'Event start time as an ISO 8601 date-time with an offset (e.g. 2026-07-01T15:00:00Z). For all-day events pass a date (e.g. 2026-07-01).', + ), + endsAt: z + .string() + .describe( + 'Event end time as an ISO 8601 date-time with an offset, after startsAt. For all-day events pass the exclusive end date (the day after the last day).', + ), + isFullDay: z + .boolean() + .describe('Whether the event lasts the whole day') + .default(false), + timeZone: z + .string() + .describe( + 'IANA time zone for the event (e.g. America/New_York). Defaults to UTC.', + ) + .optional(), + attendees: z + .string() + .describe( + 'Comma-separated attendee email addresses. Only applied when sendInvitations is true; otherwise ignored.', + ) + .optional() + .default(''), + sendInvitations: z + .boolean() + .describe( + 'When true, attendees are added to the event and emailed an invitation. When false, the event is created with no attendees and nobody is notified.', + ) + .default(false), + addConferencing: z + .boolean() + .describe( + 'When true, a video conferencing link is generated (Google Meet for Google accounts, Microsoft Teams for Microsoft accounts).', + ) + .default(false), + connectedAccountId: z + .string() + .refine((val) => isValidUuid(val)) + .describe( + 'The UUID of the connected account to create the event on. Provide only if known; otherwise leave blank to use the default calendar account.', + ) + .optional(), +}); diff --git a/packages/twenty-server/src/engine/core-modules/tool/tools/calendar-tool/create-calendar-event-tool.ts b/packages/twenty-server/src/engine/core-modules/tool/tools/calendar-tool/create-calendar-event-tool.ts new file mode 100644 index 0000000000..29f36c937a --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/tool/tools/calendar-tool/create-calendar-event-tool.ts @@ -0,0 +1,97 @@ +import { Injectable, Logger } from '@nestjs/common'; + +import { PermissionFlagType } from 'twenty-shared/constants'; + +import { CreateCalendarEventToolInputZodSchema } from 'src/engine/core-modules/tool/tools/calendar-tool/calendar-tool.schema'; +import { type CreateCalendarEventToolInput } from 'src/engine/core-modules/tool/tools/calendar-tool/types/create-calendar-event-tool-input.type'; +import { type ToolExecutionContext } from 'src/engine/core-modules/tool/types/tool-execution-context.type'; +import { type ToolOutput } from 'src/engine/core-modules/tool/types/tool-output.type'; +import { type Tool } from 'src/engine/core-modules/tool/types/tool.type'; +import { CalendarEventCreationException } from 'src/modules/calendar/calendar-event-creation-manager/exceptions/calendar-event-creation.exception'; +import { CalendarEventComposerService } from 'src/modules/calendar/calendar-event-creation-manager/services/calendar-event-composer.service'; +import { CreateCalendarEventService } from 'src/modules/calendar/calendar-event-creation-manager/services/create-calendar-event.service'; + +@Injectable() +export class CreateCalendarEventTool implements Tool { + private readonly logger = new Logger(CreateCalendarEventTool.name); + + description = + 'Create a calendar event on a connected Google or Microsoft account. Requires CREATE_CALENDAR_EVENT_TOOL permission. Set sendInvitations to true to attach attendees and email them an invitation; when false the event is created with no attendees and nobody is notified.'; + inputSchema = CreateCalendarEventToolInputZodSchema; + flag = PermissionFlagType.CREATE_CALENDAR_EVENT_TOOL; + + constructor( + private readonly calendarEventComposerService: CalendarEventComposerService, + private readonly createCalendarEventService: CreateCalendarEventService, + ) {} + + async execute( + parameters: CreateCalendarEventToolInput, + context: ToolExecutionContext, + ): Promise { + try { + const result = + await this.calendarEventComposerService.composeCalendarEvent( + parameters, + context.workspaceId, + ); + + if (!result.success) { + return { + success: false, + message: 'Failed to create calendar event', + error: result.error, + }; + } + + const { data } = result; + + const createdEvent = + await this.createCalendarEventService.createComposedCalendarEvent(data); + + await this.createCalendarEventService.persistCalendarEvent( + createdEvent, + data, + context.workspaceId, + ); + + this.logger.log( + `Calendar event "${createdEvent.title}" created on connected account ${data.connectedAccount.id}`, + ); + + return { + success: true, + message: `Calendar event "${createdEvent.title}" created`, + result: { + iCalUid: createdEvent.iCalUid, + externalEventId: createdEvent.id, + title: createdEvent.title, + startsAt: createdEvent.startsAt, + endsAt: createdEvent.endsAt, + conferenceLink: createdEvent.conferenceLinkUrl || undefined, + attendeeCount: createdEvent.participants.length, + connectedAccountId: data.connectedAccount.id, + }, + }; + } catch (error) { + if (error instanceof CalendarEventCreationException) { + return { + success: false, + message: 'Failed to create calendar event', + error: error.message, + }; + } + + this.logger.error(`Failed to create calendar event: ${error}`); + + return { + success: false, + message: 'Failed to create calendar event', + error: + error instanceof Error + ? error.message + : 'Failed to create calendar event', + }; + } + } +} diff --git a/packages/twenty-server/src/engine/core-modules/tool/tools/calendar-tool/types/create-calendar-event-tool-input.type.ts b/packages/twenty-server/src/engine/core-modules/tool/tools/calendar-tool/types/create-calendar-event-tool-input.type.ts new file mode 100644 index 0000000000..0a8b503e4b --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/tool/tools/calendar-tool/types/create-calendar-event-tool-input.type.ts @@ -0,0 +1,7 @@ +import { type z } from 'zod'; + +import { type CreateCalendarEventToolInputZodSchema } from 'src/engine/core-modules/tool/tools/calendar-tool/calendar-tool.schema'; + +export type CreateCalendarEventToolInput = z.infer< + typeof CreateCalendarEventToolInputZodSchema +>; diff --git a/packages/twenty-server/src/engine/metadata-modules/permission-flag/constants/standard-permission-flag-definitions.constant.ts b/packages/twenty-server/src/engine/metadata-modules/permission-flag/constants/standard-permission-flag-definitions.constant.ts index b1d296b371..8fb2aff1d0 100644 --- a/packages/twenty-server/src/engine/metadata-modules/permission-flag/constants/standard-permission-flag-definitions.constant.ts +++ b/packages/twenty-server/src/engine/metadata-modules/permission-flag/constants/standard-permission-flag-definitions.constant.ts @@ -119,6 +119,11 @@ const STANDARD_PERMISSION_FLAG_METADATA: Record< description: 'Send emails via connected accounts', icon: 'IconMail', }, + [PermissionFlagType.CREATE_CALENDAR_EVENT_TOOL]: { + label: 'Create Calendar Event', + description: 'Create calendar events via connected accounts', + icon: 'IconCalendarEvent', + }, [PermissionFlagType.HTTP_REQUEST_TOOL]: { label: 'HTTP Request', description: 'Make HTTP requests to external APIs', diff --git a/packages/twenty-server/src/engine/metadata-modules/permissions/constants/tool-permission-flags.ts b/packages/twenty-server/src/engine/metadata-modules/permissions/constants/tool-permission-flags.ts index 534e35d73f..126a3cf163 100644 --- a/packages/twenty-server/src/engine/metadata-modules/permissions/constants/tool-permission-flags.ts +++ b/packages/twenty-server/src/engine/metadata-modules/permissions/constants/tool-permission-flags.ts @@ -4,6 +4,7 @@ export const TOOL_PERMISSION_FLAGS = [ 'UPLOAD_FILE', 'DOWNLOAD_FILE', 'SEND_EMAIL_TOOL', + 'CREATE_CALENDAR_EVENT_TOOL', 'HTTP_REQUEST_TOOL', 'IMPORT_CSV', 'EXPORT_CSV', diff --git a/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.service.ts b/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.service.ts index 14b8cb9c0e..ec78c4c647 100644 --- a/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.service.ts @@ -119,6 +119,7 @@ export class PermissionsService { [PermissionFlagType.UPLOAD_FILE]: false, [PermissionFlagType.DOWNLOAD_FILE]: false, [PermissionFlagType.SEND_EMAIL_TOOL]: false, + [PermissionFlagType.CREATE_CALENDAR_EVENT_TOOL]: false, [PermissionFlagType.HTTP_REQUEST_TOOL]: false, [PermissionFlagType.CODE_INTERPRETER_TOOL]: false, [PermissionFlagType.IMPORT_CSV]: false, diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/calendar-event-creation-manager.module.ts b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/calendar-event-creation-manager.module.ts new file mode 100644 index 0000000000..8e2786fc29 --- /dev/null +++ b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/calendar-event-creation-manager.module.ts @@ -0,0 +1,31 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; +import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; +import { CalDavCreateEventService } from 'src/modules/calendar/calendar-event-creation-manager/drivers/caldav/services/caldav-create-event.service'; +import { GoogleCalendarCreateEventService } from 'src/modules/calendar/calendar-event-creation-manager/drivers/google-calendar/services/google-calendar-create-event.service'; +import { MicrosoftCalendarCreateEventService } from 'src/modules/calendar/calendar-event-creation-manager/drivers/microsoft-calendar/services/microsoft-calendar-create-event.service'; +import { CalendarEventComposerService } from 'src/modules/calendar/calendar-event-creation-manager/services/calendar-event-composer.service'; +import { CreateCalendarEventService } from 'src/modules/calendar/calendar-event-creation-manager/services/create-calendar-event.service'; +import { CalDavDriverModule } from 'src/modules/calendar/calendar-event-import-manager/drivers/caldav/caldav-driver.module'; +import { CalendarEventImportManagerModule } from 'src/modules/calendar/calendar-event-import-manager/calendar-event-import-manager.module'; +import { OAuth2ClientManagerModule } from 'src/modules/connected-account/oauth2-client-manager/oauth2-client-manager.module'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([ConnectedAccountEntity, CalendarChannelEntity]), + OAuth2ClientManagerModule, + CalendarEventImportManagerModule, + CalDavDriverModule, + ], + providers: [ + CalendarEventComposerService, + CreateCalendarEventService, + GoogleCalendarCreateEventService, + MicrosoftCalendarCreateEventService, + CalDavCreateEventService, + ], + exports: [CalendarEventComposerService, CreateCalendarEventService], +}) +export class CalendarEventCreationManagerModule {} diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/create-calendar-event.module.ts b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/create-calendar-event.module.ts new file mode 100644 index 0000000000..9218b4c86f --- /dev/null +++ b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/create-calendar-event.module.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common'; + +import { ConnectedAccountMetadataModule } from 'src/engine/metadata-modules/connected-account/connected-account-metadata.module'; +import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module'; +import { CalendarEventCreationManagerModule } from 'src/modules/calendar/calendar-event-creation-manager/calendar-event-creation-manager.module'; +import { CreateCalendarEventResolver } from 'src/modules/calendar/calendar-event-creation-manager/resolvers/create-calendar-event.resolver'; + +@Module({ + imports: [ + CalendarEventCreationManagerModule, + ConnectedAccountMetadataModule, + PermissionsModule, + ], + providers: [CreateCalendarEventResolver], +}) +export class CreateCalendarEventModule {} diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/drivers/caldav/services/__tests__/caldav-create-event.service.spec.ts b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/drivers/caldav/services/__tests__/caldav-create-event.service.spec.ts new file mode 100644 index 0000000000..7631ed92a1 --- /dev/null +++ b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/drivers/caldav/services/__tests__/caldav-create-event.service.spec.ts @@ -0,0 +1,108 @@ +import { Test, type TestingModule } from '@nestjs/testing'; + +import { ConnectedAccountProvider } from 'twenty-shared/types'; + +import { CalDavCreateEventService } from 'src/modules/calendar/calendar-event-creation-manager/drivers/caldav/services/caldav-create-event.service'; +import { CalendarEventCreationException } from 'src/modules/calendar/calendar-event-creation-manager/exceptions/calendar-event-creation.exception'; +import { type CalendarEventToCreate } from 'src/modules/calendar/calendar-event-creation-manager/types/calendar-event-to-create.type'; +import { CalDavClientProvider } from 'src/modules/calendar/calendar-event-import-manager/drivers/caldav/providers/caldav-client.provider'; +import { CalDavFetchEventsService } from 'src/modules/calendar/calendar-event-import-manager/drivers/caldav/services/caldav-fetch-events.service'; + +const connectedAccount = { + id: 'account-1', + provider: ConnectedAccountProvider.IMAP_SMTP_CALDAV, +}; + +const baseInput: CalendarEventToCreate = { + title: 'Sync', + startsAt: '2026-07-01T14:00:00Z', + endsAt: '2026-07-01T15:00:00Z', + isFullDay: false, + timeZone: 'UTC', + attendees: [], + sendInvitations: false, + addConferencing: false, +}; + +const CALENDAR_URL = 'https://dav.example.com/calendars/jane/default/'; + +describe('CalDavCreateEventService', () => { + let service: CalDavCreateEventService; + const createCalendarObject = jest.fn(); + const client = { createCalendarObject }; + const getClient = jest.fn(); + const listEventCalendars = jest.fn(); + const fetchEventsByHrefs = jest.fn(); + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + CalDavCreateEventService, + { provide: CalDavClientProvider, useValue: { getClient } }, + { + provide: CalDavFetchEventsService, + useValue: { listEventCalendars, fetchEventsByHrefs }, + }, + ], + }).compile(); + + service = module.get(CalDavCreateEventService); + + getClient.mockResolvedValue(client); + listEventCalendars.mockResolvedValue([{ url: CALENDAR_URL }]); + createCalendarObject.mockResolvedValue({}); + }); + + afterEach(() => jest.clearAllMocks()); + + it('keys the event on the server-returned href, not the locally reconstructed url', async () => { + const serverHref = '/calendars/jane/default/server-assigned.ics'; + + fetchEventsByHrefs.mockResolvedValue([ + { id: serverHref, iCalUid: 'whatever' }, + ]); + + const result = await service.createCalendarEvent( + baseInput, + connectedAccount, + ); + + expect(createCalendarObject).toHaveBeenCalledTimes(1); + expect(fetchEventsByHrefs).toHaveBeenCalledWith(client, [ + expect.stringMatching(/^https:\/\/dav\.example\.com\/.*\.ics$/), + ]); + expect(result.id).toBe(serverHref); + }); + + it('falls back to the reconstructed href when the server copy is not retrievable', async () => { + fetchEventsByHrefs.mockResolvedValue([]); + + const result = await service.createCalendarEvent( + baseInput, + connectedAccount, + ); + + expect(result.id).toMatch( + /^https:\/\/dav\.example\.com\/calendars\/jane\/default\/.*\.ics$/, + ); + }); + + it('falls back to the reconstructed href when re-fetch fails, without failing the create', async () => { + fetchEventsByHrefs.mockRejectedValue(new Error('network')); + + const result = await service.createCalendarEvent( + baseInput, + connectedAccount, + ); + + expect(result.id).toMatch(/\.ics$/); + }); + + it('throws when no writable calendar is available', async () => { + listEventCalendars.mockResolvedValue([]); + + await expect( + service.createCalendarEvent(baseInput, connectedAccount), + ).rejects.toThrow(CalendarEventCreationException); + }); +}); diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/drivers/caldav/services/caldav-create-event.service.ts b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/drivers/caldav/services/caldav-create-event.service.ts new file mode 100644 index 0000000000..bdc34cba27 --- /dev/null +++ b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/drivers/caldav/services/caldav-create-event.service.ts @@ -0,0 +1,124 @@ +import { Injectable } from '@nestjs/common'; + +import ical from 'ical-generator'; +import { v4 as uuid } from 'uuid'; + +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; +import { + CalendarEventCreationException, + CalendarEventCreationExceptionCode, +} from 'src/modules/calendar/calendar-event-creation-manager/exceptions/calendar-event-creation.exception'; +import { type CalendarEventCreationDriver } from 'src/modules/calendar/calendar-event-creation-manager/interfaces/calendar-event-creation-driver.interface'; +import { type CalendarEventToCreate } from 'src/modules/calendar/calendar-event-creation-manager/types/calendar-event-to-create.type'; +import { CalDavClientProvider } from 'src/modules/calendar/calendar-event-import-manager/drivers/caldav/providers/caldav-client.provider'; +import { CalDavFetchEventsService } from 'src/modules/calendar/calendar-event-import-manager/drivers/caldav/services/caldav-fetch-events.service'; +import { type FetchedCalendarEvent } from 'src/modules/calendar/common/types/fetched-calendar-event'; + +@Injectable() +export class CalDavCreateEventService implements CalendarEventCreationDriver { + constructor( + private readonly calDavClientProvider: CalDavClientProvider, + private readonly calDavFetchEventsService: CalDavFetchEventsService, + ) {} + + async createCalendarEvent( + input: CalendarEventToCreate, + connectedAccount: Pick, + ): Promise { + try { + const client = await this.calDavClientProvider.getClient( + connectedAccount.id, + ); + + const [targetCalendar] = + await this.calDavFetchEventsService.listEventCalendars(client); + + if (!targetCalendar) { + throw new CalendarEventCreationException( + 'No writable CalDAV calendar found', + CalendarEventCreationExceptionCode.PROVIDER_REQUEST_FAILED, + ); + } + + const uid = uuid(); + const calendar = ical({ prodId: '//Twenty//Calendar//EN' }); + + calendar.createEvent({ + id: uid, + start: new Date(input.startsAt), + end: new Date(input.endsAt), + allDay: input.isFullDay, + summary: input.title, + description: input.description, + location: input.location, + attendees: input.attendees.map((attendee) => ({ + email: attendee.email, + name: attendee.displayName, + })), + }); + + const filename = `${uid}.ics`; + + await client.createCalendarObject({ + calendar: targetCalendar, + filename, + iCalString: calendar.toString(), + }); + + // Source the resource href from the server through the same path the + // import keys on, so a later sync reconciles this event instead of + // duplicating it. Reconstructing the href locally risks a format mismatch + // (full URL vs the server-relative path the provider returns). + const reconstructedHref = new URL(filename, targetCalendar.url).href; + + let id = reconstructedHref; + + try { + const [syncedEvent] = + await this.calDavFetchEventsService.fetchEventsByHrefs(client, [ + reconstructedHref, + ]); + + id = syncedEvent?.id ?? reconstructedHref; + } catch { + // The event was created; resolving its server href is best-effort, so + // fall back to the reconstructed href rather than failing the create. + } + + const now = new Date().toISOString(); + + return { + id, + iCalUid: uid, + title: input.title, + description: input.description ?? '', + location: input.location ?? '', + startsAt: input.startsAt, + endsAt: input.endsAt, + isFullDay: input.isFullDay, + isCanceled: false, + status: 'CONFIRMED', + conferenceLinkLabel: '', + conferenceLinkUrl: '', + conferenceSolution: '', + externalCreatedAt: now, + externalUpdatedAt: now, + participants: input.attendees.map((attendee) => ({ + displayName: attendee.displayName ?? '', + handle: attendee.email, + responseStatus: 'needsAction', + isOrganizer: false, + })), + }; + } catch (error) { + if (error instanceof CalendarEventCreationException) { + throw error; + } + + throw new CalendarEventCreationException( + `Failed to create CalDAV calendar event: ${error instanceof Error ? error.message : 'unknown error'}`, + CalendarEventCreationExceptionCode.PROVIDER_REQUEST_FAILED, + ); + } + } +} diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/drivers/google-calendar/services/__tests__/google-calendar-create-event.service.spec.ts b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/drivers/google-calendar/services/__tests__/google-calendar-create-event.service.spec.ts new file mode 100644 index 0000000000..78c0ff6f22 --- /dev/null +++ b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/drivers/google-calendar/services/__tests__/google-calendar-create-event.service.spec.ts @@ -0,0 +1,132 @@ +import { Test, type TestingModule } from '@nestjs/testing'; + +import { google } from 'googleapis'; +import { ConnectedAccountProvider } from 'twenty-shared/types'; + +import { GoogleCalendarCreateEventService } from 'src/modules/calendar/calendar-event-creation-manager/drivers/google-calendar/services/google-calendar-create-event.service'; +import { CalendarEventCreationException } from 'src/modules/calendar/calendar-event-creation-manager/exceptions/calendar-event-creation.exception'; +import { type CalendarEventToCreate } from 'src/modules/calendar/calendar-event-creation-manager/types/calendar-event-to-create.type'; +import { GoogleOAuth2ClientProvider } from 'src/modules/connected-account/oauth2-client-manager/drivers/google/google-oauth2-client.provider'; + +const connectedAccount = { + id: 'account-1', + provider: ConnectedAccountProvider.GOOGLE, +}; + +const baseInput: CalendarEventToCreate = { + title: 'Sync', + startsAt: '2026-07-01T14:00:00Z', + endsAt: '2026-07-01T15:00:00Z', + isFullDay: false, + timeZone: 'UTC', + attendees: [], + sendInvitations: false, + addConferencing: false, +}; + +describe('GoogleCalendarCreateEventService', () => { + let service: GoogleCalendarCreateEventService; + const insert = jest.fn(); + const get = jest.fn(); + + beforeEach(async () => { + jest + .spyOn(google, 'calendar') + .mockReturnValue({ events: { insert, get } } as never); + + const module: TestingModule = await Test.createTestingModule({ + providers: [ + GoogleCalendarCreateEventService, + { + provide: GoogleOAuth2ClientProvider, + useValue: { getClient: jest.fn().mockResolvedValue({}) }, + }, + ], + }).compile(); + + service = module.get(GoogleCalendarCreateEventService); + + insert.mockResolvedValue({ + data: { + id: 'google-event-1', + iCalUID: 'google-event-1@google.com', + summary: 'Sync', + status: 'confirmed', + start: { dateTime: '2026-07-01T14:00:00Z' }, + end: { dateTime: '2026-07-01T15:00:00Z' }, + }, + }); + }); + + afterEach(() => { + insert.mockReset(); + get.mockReset(); + jest.restoreAllMocks(); + }); + + it('inserts the event without notifications or conferencing by default', async () => { + const result = await service.createCalendarEvent( + baseInput, + connectedAccount, + ); + + expect(insert).toHaveBeenCalledWith( + expect.objectContaining({ + calendarId: 'primary', + conferenceDataVersion: 0, + sendUpdates: 'none', + requestBody: expect.objectContaining({ summary: 'Sync' }), + }), + ); + expect(get).not.toHaveBeenCalled(); + expect(result.id).toBe('google-event-1'); + expect(result.iCalUid).toBe('google-event-1@google.com'); + }); + + it('notifies attendees and requests conferencing when enabled', async () => { + await service.createCalendarEvent( + { ...baseInput, sendInvitations: true, addConferencing: true }, + connectedAccount, + ); + + expect(insert).toHaveBeenCalledWith( + expect.objectContaining({ + conferenceDataVersion: 1, + sendUpdates: 'all', + }), + ); + }); + + it('returns the Meet link from the insert response without re-fetching', async () => { + insert.mockResolvedValue({ + data: { + id: 'google-event-1', + iCalUID: 'google-event-1@google.com', + status: 'confirmed', + start: { dateTime: '2026-07-01T14:00:00Z' }, + end: { dateTime: '2026-07-01T15:00:00Z' }, + conferenceData: { + entryPoints: [{ uri: 'https://meet.google.com/abc-defg-hij' }], + }, + }, + }); + + const result = await service.createCalendarEvent( + { ...baseInput, addConferencing: true }, + connectedAccount, + ); + + expect(get).not.toHaveBeenCalled(); + expect(result.conferenceLinkUrl).toBe( + 'https://meet.google.com/abc-defg-hij', + ); + }); + + it('wraps provider errors in a CalendarEventCreationException', async () => { + insert.mockRejectedValue(new Error('boom')); + + await expect( + service.createCalendarEvent(baseInput, connectedAccount), + ).rejects.toThrow(CalendarEventCreationException); + }); +}); diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/drivers/google-calendar/services/google-calendar-create-event.service.ts b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/drivers/google-calendar/services/google-calendar-create-event.service.ts new file mode 100644 index 0000000000..58fcef511a --- /dev/null +++ b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/drivers/google-calendar/services/google-calendar-create-event.service.ts @@ -0,0 +1,54 @@ +import { Injectable } from '@nestjs/common'; + +import { google } from 'googleapis'; + +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; +import { formatGoogleCalendarEvents } from 'src/modules/calendar/calendar-event-import-manager/drivers/google-calendar/utils/format-google-calendar-event.util'; +import { toGoogleEventInput } from 'src/modules/calendar/calendar-event-creation-manager/drivers/utils/to-google-event-input.util'; +import { + CalendarEventCreationException, + CalendarEventCreationExceptionCode, +} from 'src/modules/calendar/calendar-event-creation-manager/exceptions/calendar-event-creation.exception'; +import { type CalendarEventCreationDriver } from 'src/modules/calendar/calendar-event-creation-manager/interfaces/calendar-event-creation-driver.interface'; +import { type CalendarEventToCreate } from 'src/modules/calendar/calendar-event-creation-manager/types/calendar-event-to-create.type'; +import { type FetchedCalendarEvent } from 'src/modules/calendar/common/types/fetched-calendar-event'; +import { GoogleOAuth2ClientProvider } from 'src/modules/connected-account/oauth2-client-manager/drivers/google/google-oauth2-client.provider'; + +const GOOGLE_CALENDAR_ID = 'primary'; + +@Injectable() +export class GoogleCalendarCreateEventService implements CalendarEventCreationDriver { + constructor( + private readonly googleOAuth2ClientProvider: GoogleOAuth2ClientProvider, + ) {} + + async createCalendarEvent( + input: CalendarEventToCreate, + connectedAccount: Pick, + ): Promise { + const oAuth2Client = await this.googleOAuth2ClientProvider.getClient( + connectedAccount.id, + ); + + const googleCalendarClient = google.calendar({ + version: 'v3', + auth: oAuth2Client, + }); + + try { + const { data } = await googleCalendarClient.events.insert({ + calendarId: GOOGLE_CALENDAR_ID, + conferenceDataVersion: input.addConferencing ? 1 : 0, + sendUpdates: input.sendInvitations ? 'all' : 'none', + requestBody: toGoogleEventInput(input), + }); + + return formatGoogleCalendarEvents([data])[0]; + } catch (error) { + throw new CalendarEventCreationException( + `Failed to create Google calendar event: ${error instanceof Error ? error.message : 'unknown error'}`, + CalendarEventCreationExceptionCode.PROVIDER_REQUEST_FAILED, + ); + } + } +} diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/drivers/microsoft-calendar/services/__tests__/microsoft-calendar-create-event.service.spec.ts b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/drivers/microsoft-calendar/services/__tests__/microsoft-calendar-create-event.service.spec.ts new file mode 100644 index 0000000000..c1cd4e9b53 --- /dev/null +++ b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/drivers/microsoft-calendar/services/__tests__/microsoft-calendar-create-event.service.spec.ts @@ -0,0 +1,80 @@ +import { Test, type TestingModule } from '@nestjs/testing'; + +import { ConnectedAccountProvider } from 'twenty-shared/types'; + +import { MicrosoftCalendarCreateEventService } from 'src/modules/calendar/calendar-event-creation-manager/drivers/microsoft-calendar/services/microsoft-calendar-create-event.service'; +import { CalendarEventCreationException } from 'src/modules/calendar/calendar-event-creation-manager/exceptions/calendar-event-creation.exception'; +import { type CalendarEventToCreate } from 'src/modules/calendar/calendar-event-creation-manager/types/calendar-event-to-create.type'; +import { MicrosoftOAuth2ClientProvider } from 'src/modules/connected-account/oauth2-client-manager/drivers/microsoft/microsoft-oauth2-client.provider'; + +const connectedAccount = { + id: 'account-1', + provider: ConnectedAccountProvider.MICROSOFT, +}; + +const baseInput: CalendarEventToCreate = { + title: 'Sync', + startsAt: '2026-07-01T14:00:00Z', + endsAt: '2026-07-01T15:00:00Z', + isFullDay: false, + timeZone: 'UTC', + attendees: [], + sendInvitations: false, + addConferencing: false, +}; + +describe('MicrosoftCalendarCreateEventService', () => { + let service: MicrosoftCalendarCreateEventService; + const post = jest.fn(); + const header = jest.fn(); + const request = { header, post }; + header.mockReturnValue(request); + const api = jest.fn().mockReturnValue(request); + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + MicrosoftCalendarCreateEventService, + { + provide: MicrosoftOAuth2ClientProvider, + useValue: { getClient: jest.fn().mockResolvedValue({ api }) }, + }, + ], + }).compile(); + + service = module.get(MicrosoftCalendarCreateEventService); + + post.mockResolvedValue({ + id: 'microsoft-event-1', + iCalUId: 'microsoft-event-1@outlook.com', + subject: 'Sync', + start: { dateTime: '2026-07-01T14:00:00.0000000', timeZone: 'UTC' }, + end: { dateTime: '2026-07-01T15:00:00.0000000', timeZone: 'UTC' }, + }); + }); + + afterEach(() => jest.clearAllMocks()); + + it('posts the mapped event to the calendar events endpoint', async () => { + const result = await service.createCalendarEvent( + baseInput, + connectedAccount, + ); + + expect(api).toHaveBeenCalledWith('/me/calendar/events'); + expect(header).toHaveBeenCalledWith('Prefer', 'outlook.timezone="UTC"'); + expect(post).toHaveBeenCalledWith( + expect.objectContaining({ subject: 'Sync', isAllDay: false }), + ); + expect(result.id).toBe('microsoft-event-1'); + expect(result.iCalUid).toBe('microsoft-event-1@outlook.com'); + }); + + it('wraps provider errors in a CalendarEventCreationException', async () => { + post.mockRejectedValue(new Error('boom')); + + await expect( + service.createCalendarEvent(baseInput, connectedAccount), + ).rejects.toThrow(CalendarEventCreationException); + }); +}); diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/drivers/microsoft-calendar/services/microsoft-calendar-create-event.service.ts b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/drivers/microsoft-calendar/services/microsoft-calendar-create-event.service.ts new file mode 100644 index 0000000000..e7b0b1e13a --- /dev/null +++ b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/drivers/microsoft-calendar/services/microsoft-calendar-create-event.service.ts @@ -0,0 +1,49 @@ +import { Injectable } from '@nestjs/common'; + +import { type Event } from '@microsoft/microsoft-graph-types'; + +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; +import { formatMicrosoftCalendarEvents } from 'src/modules/calendar/calendar-event-import-manager/drivers/microsoft-calendar/utils/format-microsoft-calendar-event.util'; +import { toMicrosoftEventInput } from 'src/modules/calendar/calendar-event-creation-manager/drivers/utils/to-microsoft-event-input.util'; +import { + CalendarEventCreationException, + CalendarEventCreationExceptionCode, +} from 'src/modules/calendar/calendar-event-creation-manager/exceptions/calendar-event-creation.exception'; +import { type CalendarEventCreationDriver } from 'src/modules/calendar/calendar-event-creation-manager/interfaces/calendar-event-creation-driver.interface'; +import { type CalendarEventToCreate } from 'src/modules/calendar/calendar-event-creation-manager/types/calendar-event-to-create.type'; +import { type FetchedCalendarEvent } from 'src/modules/calendar/common/types/fetched-calendar-event'; +import { MicrosoftOAuth2ClientProvider } from 'src/modules/connected-account/oauth2-client-manager/drivers/microsoft/microsoft-oauth2-client.provider'; + +@Injectable() +export class MicrosoftCalendarCreateEventService implements CalendarEventCreationDriver { + constructor( + private readonly microsoftOAuth2ClientProvider: MicrosoftOAuth2ClientProvider, + ) {} + + async createCalendarEvent( + input: CalendarEventToCreate, + connectedAccount: Pick, + ): Promise { + const microsoftClient = await this.microsoftOAuth2ClientProvider.getClient( + connectedAccount.id, + ); + + try { + // Request the created event back in UTC so its start/end are absolute + // instants. Graph otherwise echoes the request time zone, which the shared + // formatter would persist as an ambiguous wall-clock time. This matches how + // the import path consumes Graph datetimes. + const createdEvent: Event = await microsoftClient + .api('/me/calendar/events') + .header('Prefer', 'outlook.timezone="UTC"') + .post(toMicrosoftEventInput(input)); + + return formatMicrosoftCalendarEvents([createdEvent])[0]; + } catch (error) { + throw new CalendarEventCreationException( + `Failed to create Microsoft calendar event: ${error instanceof Error ? error.message : 'unknown error'}`, + CalendarEventCreationExceptionCode.PROVIDER_REQUEST_FAILED, + ); + } + } +} diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/drivers/utils/__tests__/to-google-event-input.util.spec.ts b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/drivers/utils/__tests__/to-google-event-input.util.spec.ts new file mode 100644 index 0000000000..641fb99920 --- /dev/null +++ b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/drivers/utils/__tests__/to-google-event-input.util.spec.ts @@ -0,0 +1,65 @@ +import { toGoogleEventInput } from 'src/modules/calendar/calendar-event-creation-manager/drivers/utils/to-google-event-input.util'; +import { type CalendarEventToCreate } from 'src/modules/calendar/calendar-event-creation-manager/types/calendar-event-to-create.type'; + +const baseInput: CalendarEventToCreate = { + title: 'Team Sync', + description: 'Weekly sync', + location: 'Room A', + startsAt: '2026-07-01T14:00:00Z', + endsAt: '2026-07-01T15:00:00Z', + isFullDay: false, + timeZone: 'America/New_York', + attendees: [], + sendInvitations: false, + addConferencing: false, +}; + +describe('toGoogleEventInput', () => { + it('maps a timed event with a dateTime and time zone', () => { + const event = toGoogleEventInput(baseInput); + + expect(event).toMatchObject({ + summary: 'Team Sync', + description: 'Weekly sync', + location: 'Room A', + start: { dateTime: '2026-07-01T14:00:00Z', timeZone: 'America/New_York' }, + end: { dateTime: '2026-07-01T15:00:00Z', timeZone: 'America/New_York' }, + }); + expect(event.attendees).toBeUndefined(); + expect(event.conferenceData).toBeUndefined(); + }); + + it('maps an all-day event with date-only boundaries and no time zone', () => { + const event = toGoogleEventInput({ + ...baseInput, + isFullDay: true, + startsAt: '2026-07-01T00:00:00Z', + endsAt: '2026-07-02T00:00:00Z', + }); + + expect(event.start).toEqual({ date: '2026-07-01' }); + expect(event.end).toEqual({ date: '2026-07-02' }); + }); + + it('maps attendees when present', () => { + const event = toGoogleEventInput({ + ...baseInput, + attendees: [{ email: 'guest@example.com', displayName: 'Guest' }], + }); + + expect(event.attendees).toEqual([ + { email: 'guest@example.com', displayName: 'Guest' }, + ]); + }); + + it('adds a Google Meet conference request when conferencing is enabled', () => { + const event = toGoogleEventInput({ ...baseInput, addConferencing: true }); + + expect(event.conferenceData?.createRequest?.conferenceSolutionKey).toEqual({ + type: 'hangoutsMeet', + }); + expect(event.conferenceData?.createRequest?.requestId).toEqual( + expect.any(String), + ); + }); +}); diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/drivers/utils/__tests__/to-microsoft-event-input.util.spec.ts b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/drivers/utils/__tests__/to-microsoft-event-input.util.spec.ts new file mode 100644 index 0000000000..16a66a2eeb --- /dev/null +++ b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/drivers/utils/__tests__/to-microsoft-event-input.util.spec.ts @@ -0,0 +1,85 @@ +import { toMicrosoftEventInput } from 'src/modules/calendar/calendar-event-creation-manager/drivers/utils/to-microsoft-event-input.util'; +import { type CalendarEventToCreate } from 'src/modules/calendar/calendar-event-creation-manager/types/calendar-event-to-create.type'; + +const baseInput: CalendarEventToCreate = { + title: 'Team Sync', + description: 'Weekly sync', + location: 'Room A', + startsAt: '2026-07-01T14:00:00Z', + endsAt: '2026-07-01T15:00:00Z', + isFullDay: false, + timeZone: 'America/New_York', + attendees: [], + sendInvitations: false, + addConferencing: false, +}; + +describe('toMicrosoftEventInput', () => { + it('converts the absolute instant to wall-clock in the event time zone (Graph ignores the offset)', () => { + // 14:00Z / 15:00Z in July is 10:00 / 11:00 in America/New_York (EDT, UTC-4). + const event = toMicrosoftEventInput(baseInput); + + expect(event).toMatchObject({ + subject: 'Team Sync', + body: { contentType: 'text', content: 'Weekly sync' }, + start: { dateTime: '2026-07-01T10:00:00', timeZone: 'America/New_York' }, + end: { dateTime: '2026-07-01T11:00:00', timeZone: 'America/New_York' }, + isAllDay: false, + location: { displayName: 'Room A' }, + }); + expect(event.attendees).toBeUndefined(); + expect(event.isOnlineMeeting).toBeUndefined(); + }); + + it('keeps the wall-clock unchanged when the event time zone is UTC', () => { + const event = toMicrosoftEventInput({ ...baseInput, timeZone: 'UTC' }); + + expect(event.start).toEqual({ + dateTime: '2026-07-01T14:00:00', + timeZone: 'UTC', + }); + }); + + it('pins all-day events to midnight in the event time zone', () => { + const event = toMicrosoftEventInput({ + ...baseInput, + isFullDay: true, + startsAt: '2026-07-01T09:30:00Z', + endsAt: '2026-07-02T09:30:00Z', + }); + + expect(event.isAllDay).toBe(true); + expect(event.start).toEqual({ + dateTime: '2026-07-01T00:00:00', + timeZone: 'America/New_York', + }); + expect(event.end).toEqual({ + dateTime: '2026-07-02T00:00:00', + timeZone: 'America/New_York', + }); + }); + + it('maps attendees as required participants', () => { + const event = toMicrosoftEventInput({ + ...baseInput, + attendees: [{ email: 'guest@example.com', displayName: 'Guest' }], + }); + + expect(event.attendees).toEqual([ + { + emailAddress: { address: 'guest@example.com', name: 'Guest' }, + type: 'required', + }, + ]); + }); + + it('requests a Teams meeting when conferencing is enabled', () => { + const event = toMicrosoftEventInput({ + ...baseInput, + addConferencing: true, + }); + + expect(event.isOnlineMeeting).toBe(true); + expect(event.onlineMeetingProvider).toBe('teamsForBusiness'); + }); +}); diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/drivers/utils/to-google-event-input.util.ts b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/drivers/utils/to-google-event-input.util.ts new file mode 100644 index 0000000000..a1de8e14da --- /dev/null +++ b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/drivers/utils/to-google-event-input.util.ts @@ -0,0 +1,49 @@ +import { type calendar_v3 as calendarV3 } from 'googleapis'; +import { v4 as uuid } from 'uuid'; + +import { type CalendarEventToCreate } from 'src/modules/calendar/calendar-event-creation-manager/types/calendar-event-to-create.type'; + +// Google represents all-day events with a date-only boundary, and timed events +// with an RFC 3339 dateTime paired with an IANA time zone. +const toGoogleEventDateTime = ( + isoDateTime: string, + isFullDay: boolean, + timeZone: string, +): calendarV3.Schema$EventDateTime => + isFullDay + ? { date: isoDateTime.slice(0, 10) } + : { dateTime: isoDateTime, timeZone }; + +export const toGoogleEventInput = ( + input: CalendarEventToCreate, +): calendarV3.Schema$Event => { + const event: calendarV3.Schema$Event = { + summary: input.title, + description: input.description, + location: input.location, + start: toGoogleEventDateTime( + input.startsAt, + input.isFullDay, + input.timeZone, + ), + end: toGoogleEventDateTime(input.endsAt, input.isFullDay, input.timeZone), + }; + + if (input.attendees.length > 0) { + event.attendees = input.attendees.map((attendee) => ({ + email: attendee.email, + displayName: attendee.displayName, + })); + } + + if (input.addConferencing) { + event.conferenceData = { + createRequest: { + requestId: uuid(), + conferenceSolutionKey: { type: 'hangoutsMeet' }, + }, + }; + } + + return event; +}; diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/drivers/utils/to-microsoft-event-input.util.ts b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/drivers/utils/to-microsoft-event-input.util.ts new file mode 100644 index 0000000000..51934b0c9a --- /dev/null +++ b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/drivers/utils/to-microsoft-event-input.util.ts @@ -0,0 +1,76 @@ +import { type Event } from '@microsoft/microsoft-graph-types'; + +import { type CalendarEventToCreate } from 'src/modules/calendar/calendar-event-creation-manager/types/calendar-event-to-create.type'; + +// Microsoft Graph interprets `dateTime` as a wall-clock time in the supplied +// `timeZone` and ignores any embedded offset, so an absolute instant must be +// converted to its wall-clock representation in the event time zone. +const toWallClockInTimeZone = ( + isoInstant: string, + timeZone: string, +): string => { + const parts = new Intl.DateTimeFormat('en-US', { + timeZone, + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hourCycle: 'h23', + }).formatToParts(new Date(isoInstant)); + + const part = (type: string) => + parts.find((candidate) => candidate.type === type)?.value ?? '00'; + + return `${part('year')}-${part('month')}-${part('day')}T${part('hour')}:${part('minute')}:${part('second')}`; +}; + +// All-day events are timezone-agnostic dates pinned to midnight; timed events are +// absolute instants expressed as wall-clock in the event time zone. +const toMicrosoftEventDateTime = ( + isoDateTime: string, + isFullDay: boolean, + timeZone: string, +) => ({ + dateTime: isFullDay + ? `${isoDateTime.slice(0, 10)}T00:00:00` + : toWallClockInTimeZone(isoDateTime, timeZone), + timeZone, +}); + +export const toMicrosoftEventInput = (input: CalendarEventToCreate): Event => { + const event: Event = { + subject: input.title, + body: { contentType: 'text', content: input.description ?? '' }, + start: toMicrosoftEventDateTime( + input.startsAt, + input.isFullDay, + input.timeZone, + ), + end: toMicrosoftEventDateTime( + input.endsAt, + input.isFullDay, + input.timeZone, + ), + isAllDay: input.isFullDay, + }; + + if (input.location) { + event.location = { displayName: input.location }; + } + + if (input.attendees.length > 0) { + event.attendees = input.attendees.map((attendee) => ({ + emailAddress: { address: attendee.email, name: attendee.displayName }, + type: 'required', + })); + } + + if (input.addConferencing) { + event.isOnlineMeeting = true; + event.onlineMeetingProvider = 'teamsForBusiness'; + } + + return event; +}; diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/dtos/create-calendar-event-output.dto.ts b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/dtos/create-calendar-event-output.dto.ts new file mode 100644 index 0000000000..056bef4f79 --- /dev/null +++ b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/dtos/create-calendar-event-output.dto.ts @@ -0,0 +1,17 @@ +import { Field, ObjectType } from '@nestjs/graphql'; + +@ObjectType('CreateCalendarEventOutput') +export class CreateCalendarEventOutputDTO { + @Field(() => Boolean) + success: boolean; + + // Stable cross-provider identifier; query the created event in Twenty by iCalUid. + @Field(() => String, { nullable: true }) + iCalUid?: string; + + @Field(() => String, { nullable: true }) + conferenceLink?: string; + + @Field(() => String, { nullable: true }) + error?: string; +} diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/dtos/create-calendar-event.input.ts b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/dtos/create-calendar-event.input.ts new file mode 100644 index 0000000000..2de74a43d0 --- /dev/null +++ b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/dtos/create-calendar-event.input.ts @@ -0,0 +1,38 @@ +import { Field, InputType } from '@nestjs/graphql'; + +@InputType() +export class CreateCalendarEventInput { + @Field(() => String) + connectedAccountId: string; + + @Field(() => String) + title: string; + + @Field(() => String, { nullable: true }) + description?: string; + + @Field(() => String, { nullable: true }) + location?: string; + + @Field(() => String) + startsAt: string; + + @Field(() => String) + endsAt: string; + + @Field(() => Boolean, { nullable: true }) + isFullDay?: boolean; + + @Field(() => String, { nullable: true }) + timeZone?: string; + + // Comma-separated attendee email addresses. + @Field(() => String, { nullable: true }) + attendees?: string; + + @Field(() => Boolean, { nullable: true }) + sendInvitations?: boolean; + + @Field(() => Boolean, { nullable: true }) + addConferencing?: boolean; +} diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/exceptions/calendar-event-creation.exception.ts b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/exceptions/calendar-event-creation.exception.ts new file mode 100644 index 0000000000..8a86e72f49 --- /dev/null +++ b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/exceptions/calendar-event-creation.exception.ts @@ -0,0 +1,37 @@ +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; +import { assertUnreachable } from 'twenty-shared/utils'; + +import { CustomException } from 'src/utils/custom-exception'; + +export enum CalendarEventCreationExceptionCode { + PROVIDER_NOT_SUPPORTED = 'PROVIDER_NOT_SUPPORTED', + PROVIDER_REQUEST_FAILED = 'PROVIDER_REQUEST_FAILED', +} + +const getCalendarEventCreationExceptionUserFriendlyMessage = ( + code: CalendarEventCreationExceptionCode, +) => { + switch (code) { + case CalendarEventCreationExceptionCode.PROVIDER_NOT_SUPPORTED: + return msg`Calendar event creation is not supported for this account.`; + case CalendarEventCreationExceptionCode.PROVIDER_REQUEST_FAILED: + return msg`The calendar provider rejected the event creation request.`; + default: + assertUnreachable(code); + } +}; + +export class CalendarEventCreationException extends CustomException { + constructor( + message: string, + code: CalendarEventCreationExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? + getCalendarEventCreationExceptionUserFriendlyMessage(code), + }); + } +} diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/interfaces/calendar-event-creation-driver.interface.ts b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/interfaces/calendar-event-creation-driver.interface.ts new file mode 100644 index 0000000000..2363f68947 --- /dev/null +++ b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/interfaces/calendar-event-creation-driver.interface.ts @@ -0,0 +1,10 @@ +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; +import { type CalendarEventToCreate } from 'src/modules/calendar/calendar-event-creation-manager/types/calendar-event-to-create.type'; +import { type FetchedCalendarEvent } from 'src/modules/calendar/common/types/fetched-calendar-event'; + +export type CalendarEventCreationDriver = { + createCalendarEvent( + input: CalendarEventToCreate, + connectedAccount: Pick, + ): Promise; +}; diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/resolvers/create-calendar-event.resolver.ts b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/resolvers/create-calendar-event.resolver.ts new file mode 100644 index 0000000000..f7a57c58ba --- /dev/null +++ b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/resolvers/create-calendar-event.resolver.ts @@ -0,0 +1,112 @@ +import { + ForbiddenException, + Logger, + UseFilters, + UseGuards, + UsePipes, +} from '@nestjs/common'; +import { Args, Mutation } from '@nestjs/graphql'; + +import { PermissionFlagType } from 'twenty-shared/constants'; + +import { MetadataResolver } from 'src/engine/api/graphql/graphql-config/decorators/metadata-resolver.decorator'; +import { AuthGraphqlApiExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-graphql-api-exception.filter'; +import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe'; +import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; +import { AuthUserWorkspaceId } from 'src/engine/decorators/auth/auth-user-workspace-id.decorator'; +import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; +import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.guard'; +import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; +import { ConnectedAccountMetadataService } from 'src/engine/metadata-modules/connected-account/connected-account-metadata.service'; +import { CreateCalendarEventOutputDTO } from 'src/modules/calendar/calendar-event-creation-manager/dtos/create-calendar-event-output.dto'; +import { CreateCalendarEventInput } from 'src/modules/calendar/calendar-event-creation-manager/dtos/create-calendar-event.input'; +import { CalendarEventComposerService } from 'src/modules/calendar/calendar-event-creation-manager/services/calendar-event-composer.service'; +import { CreateCalendarEventService } from 'src/modules/calendar/calendar-event-creation-manager/services/create-calendar-event.service'; + +@MetadataResolver() +@UsePipes(ResolverValidationPipe) +@UseFilters(AuthGraphqlApiExceptionFilter) +@UseGuards( + WorkspaceAuthGuard, + SettingsPermissionGuard(PermissionFlagType.CREATE_CALENDAR_EVENT_TOOL), +) +export class CreateCalendarEventResolver { + private readonly logger = new Logger(CreateCalendarEventResolver.name); + + constructor( + private readonly connectedAccountMetadataService: ConnectedAccountMetadataService, + private readonly calendarEventComposerService: CalendarEventComposerService, + private readonly createCalendarEventService: CreateCalendarEventService, + ) {} + + @Mutation(() => CreateCalendarEventOutputDTO) + async createCalendarEvent( + @Args('input') input: CreateCalendarEventInput, + @AuthWorkspace() workspace: WorkspaceEntity, + @AuthUserWorkspaceId() userWorkspaceId: string, + ): Promise { + try { + await this.connectedAccountMetadataService.verifyOwnership({ + id: input.connectedAccountId, + userWorkspaceId, + workspaceId: workspace.id, + }); + + const result = + await this.calendarEventComposerService.composeCalendarEvent( + { + connectedAccountId: input.connectedAccountId, + title: input.title, + description: input.description, + location: input.location, + startsAt: input.startsAt, + endsAt: input.endsAt, + isFullDay: input.isFullDay, + timeZone: input.timeZone, + attendees: input.attendees, + sendInvitations: input.sendInvitations, + addConferencing: input.addConferencing, + }, + workspace.id, + ); + + if (!result.success) { + return { + success: false, + error: result.error, + }; + } + + const createdEvent = + await this.createCalendarEventService.createComposedCalendarEvent( + result.data, + ); + + await this.createCalendarEventService.persistCalendarEvent( + createdEvent, + result.data, + workspace.id, + ); + + return { + success: true, + iCalUid: createdEvent.iCalUid || undefined, + conferenceLink: createdEvent.conferenceLinkUrl || undefined, + }; + } catch (error) { + if (error instanceof ForbiddenException) { + throw error; + } + + this.logger.error(`Failed to create calendar event: ${error}`); + + return { + success: false, + error: + error instanceof Error + ? error.message + : 'Failed to create calendar event', + }; + } + } +} diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/services/__tests__/calendar-event-composer.service.spec.ts b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/services/__tests__/calendar-event-composer.service.spec.ts new file mode 100644 index 0000000000..697e725b7d --- /dev/null +++ b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/services/__tests__/calendar-event-composer.service.spec.ts @@ -0,0 +1,255 @@ +import { Test, type TestingModule } from '@nestjs/testing'; +import { getRepositoryToken } from '@nestjs/typeorm'; + +import { ConnectedAccountProvider } from 'twenty-shared/types'; + +import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; +import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; +import { CalendarEventComposerService } from 'src/modules/calendar/calendar-event-creation-manager/services/calendar-event-composer.service'; +import { type ComposeCalendarEventParams } from 'src/modules/calendar/calendar-event-creation-manager/types/compose-calendar-event-params.type'; + +const GOOGLE_SCOPE = 'https://www.googleapis.com/auth/calendar.events'; +const WORKSPACE_ID = 'workspace-1'; +const ACCOUNT_ID = '11111111-1111-4111-8111-111111111111'; + +const googleAccount = { + id: ACCOUNT_ID, + provider: ConnectedAccountProvider.GOOGLE, + scopes: [GOOGLE_SCOPE], + archivedAt: null, +} as ConnectedAccountEntity; + +const calendarChannel = { id: 'channel-1' } as CalendarChannelEntity; + +const validParams: ComposeCalendarEventParams = { + title: 'Sync', + startsAt: '2026-07-01T14:00:00Z', + endsAt: '2026-07-01T15:00:00Z', + connectedAccountId: ACCOUNT_ID, +}; + +describe('CalendarEventComposerService', () => { + let service: CalendarEventComposerService; + const connectedAccountFindOne = jest.fn(); + const calendarChannelFindOne = jest.fn(); + const calendarChannelFind = jest.fn(); + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + CalendarEventComposerService, + { + provide: getRepositoryToken(ConnectedAccountEntity), + useValue: { findOne: connectedAccountFindOne }, + }, + { + provide: getRepositoryToken(CalendarChannelEntity), + useValue: { + findOne: calendarChannelFindOne, + find: calendarChannelFind, + }, + }, + ], + }).compile(); + + service = module.get(CalendarEventComposerService); + + connectedAccountFindOne.mockResolvedValue(googleAccount); + calendarChannelFindOne.mockResolvedValue(calendarChannel); + }); + + afterEach(() => jest.clearAllMocks()); + + it('rejects an event without a title', async () => { + const result = await service.composeCalendarEvent( + { ...validParams, title: '' }, + WORKSPACE_ID, + ); + + expect(result.success).toBe(false); + }); + + it('rejects when endsAt is not after startsAt', async () => { + const result = await service.composeCalendarEvent( + { ...validParams, endsAt: validParams.startsAt }, + WORKSPACE_ID, + ); + + expect(result.success).toBe(false); + }); + + it('rejects a whitespace-only title', async () => { + const result = await service.composeCalendarEvent( + { ...validParams, title: ' ' }, + WORKSPACE_ID, + ); + + expect(result.success).toBe(false); + }); + + it('rejects a timed event without a UTC offset', async () => { + const result = await service.composeCalendarEvent( + { + ...validParams, + startsAt: '2026-07-01T14:00:00', + endsAt: '2026-07-01T15:00:00', + }, + WORKSPACE_ID, + ); + + expect(result.success).toBe(false); + }); + + it('rejects a date-only value for a timed event', async () => { + const result = await service.composeCalendarEvent( + { ...validParams, startsAt: '2026-07-01', endsAt: '2026-07-02' }, + WORKSPACE_ID, + ); + + expect(result.success).toBe(false); + }); + + it('rejects an all-day event whose start and end fall on the same day', async () => { + const result = await service.composeCalendarEvent( + { + ...validParams, + isFullDay: true, + startsAt: '2026-07-01T00:00:00Z', + endsAt: '2026-07-01T12:00:00Z', + }, + WORKSPACE_ID, + ); + + expect(result.success).toBe(false); + }); + + it('accepts a valid multi-day all-day event', async () => { + const result = await service.composeCalendarEvent( + { + ...validParams, + isFullDay: true, + startsAt: '2026-07-01', + endsAt: '2026-07-03', + }, + WORKSPACE_ID, + ); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.input.isFullDay).toBe(true); + } + }); + + it('rejects an invalid time zone', async () => { + const result = await service.composeCalendarEvent( + { ...validParams, timeZone: 'Not/AZone' }, + WORKSPACE_ID, + ); + + expect(result.success).toBe(false); + }); + + it('rejects invalid attendee emails when sending invitations', async () => { + const result = await service.composeCalendarEvent( + { + ...validParams, + sendInvitations: true, + attendees: 'not-an-email', + }, + WORKSPACE_ID, + ); + + expect(result.success).toBe(false); + }); + + it('strips attendees entirely when invitations are not requested', async () => { + const result = await service.composeCalendarEvent( + { + ...validParams, + sendInvitations: false, + attendees: 'guest@example.com', + }, + WORKSPACE_ID, + ); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.input.attendees).toEqual([]); + } + }); + + it('parses comma-separated attendees when sending invitations', async () => { + const result = await service.composeCalendarEvent( + { + ...validParams, + sendInvitations: true, + attendees: 'a@example.com, b@example.com', + }, + WORKSPACE_ID, + ); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.input.attendees).toEqual([ + { email: 'a@example.com' }, + { email: 'b@example.com' }, + ]); + } + }); + + it('fails when the connected account does not exist', async () => { + connectedAccountFindOne.mockResolvedValue(null); + + const result = await service.composeCalendarEvent( + validParams, + WORKSPACE_ID, + ); + + expect(result.success).toBe(false); + }); + + it('fails when the account is missing the calendar write scope', async () => { + connectedAccountFindOne.mockResolvedValue({ + ...googleAccount, + scopes: ['email'], + }); + + const result = await service.composeCalendarEvent( + validParams, + WORKSPACE_ID, + ); + + expect(result.success).toBe(false); + }); + + it('resolves the account and channel for a valid request', async () => { + const result = await service.composeCalendarEvent( + validParams, + WORKSPACE_ID, + ); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.connectedAccount.id).toBe(ACCOUNT_ID); + expect(result.data.calendarChannel.id).toBe('channel-1'); + expect(result.data.input.timeZone).toBe('UTC'); + } + }); + + it('falls back to the first calendar-capable account when none is specified', async () => { + calendarChannelFind.mockResolvedValue([ + { ...calendarChannel, connectedAccount: googleAccount }, + ]); + + const result = await service.composeCalendarEvent( + { ...validParams, connectedAccountId: undefined }, + WORKSPACE_ID, + ); + + expect(calendarChannelFind).toHaveBeenCalled(); + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.connectedAccount.id).toBe(ACCOUNT_ID); + } + }); +}); diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/services/__tests__/create-calendar-event.service.spec.ts b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/services/__tests__/create-calendar-event.service.spec.ts new file mode 100644 index 0000000000..dfd3256746 --- /dev/null +++ b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/services/__tests__/create-calendar-event.service.spec.ts @@ -0,0 +1,140 @@ +import { Test, type TestingModule } from '@nestjs/testing'; + +import { ConnectedAccountProvider } from 'twenty-shared/types'; + +import { CalDavCreateEventService } from 'src/modules/calendar/calendar-event-creation-manager/drivers/caldav/services/caldav-create-event.service'; +import { GoogleCalendarCreateEventService } from 'src/modules/calendar/calendar-event-creation-manager/drivers/google-calendar/services/google-calendar-create-event.service'; +import { MicrosoftCalendarCreateEventService } from 'src/modules/calendar/calendar-event-creation-manager/drivers/microsoft-calendar/services/microsoft-calendar-create-event.service'; +import { CalendarEventCreationException } from 'src/modules/calendar/calendar-event-creation-manager/exceptions/calendar-event-creation.exception'; +import { CreateCalendarEventService } from 'src/modules/calendar/calendar-event-creation-manager/services/create-calendar-event.service'; +import { type ComposedCalendarEvent } from 'src/modules/calendar/calendar-event-creation-manager/types/composed-calendar-event.type'; +import { CalendarSaveEventsService } from 'src/modules/calendar/calendar-event-import-manager/services/calendar-save-events.service'; +import { type FetchedCalendarEvent } from 'src/modules/calendar/common/types/fetched-calendar-event'; + +const composedEvent = ( + provider: ConnectedAccountProvider, +): ComposedCalendarEvent => + ({ + input: { + title: 'Sync', + startsAt: '2026-07-01T14:00:00Z', + endsAt: '2026-07-01T15:00:00Z', + isFullDay: false, + timeZone: 'UTC', + attendees: [], + sendInvitations: false, + addConferencing: false, + }, + connectedAccount: { id: 'account-1', provider }, + calendarChannel: { id: 'channel-1' }, + }) as unknown as ComposedCalendarEvent; + +const createdEvent = { id: 'event-1' } as FetchedCalendarEvent; + +describe('CreateCalendarEventService', () => { + let service: CreateCalendarEventService; + const googleCreate = jest.fn(); + const microsoftCreate = jest.fn(); + const calDavCreate = jest.fn(); + const saveCalendarEvents = jest.fn(); + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + CreateCalendarEventService, + { + provide: GoogleCalendarCreateEventService, + useValue: { createCalendarEvent: googleCreate }, + }, + { + provide: MicrosoftCalendarCreateEventService, + useValue: { createCalendarEvent: microsoftCreate }, + }, + { + provide: CalDavCreateEventService, + useValue: { createCalendarEvent: calDavCreate }, + }, + { + provide: CalendarSaveEventsService, + useValue: { + saveCalendarEventsAndEnqueueContactCreationJob: saveCalendarEvents, + }, + }, + ], + }).compile(); + + service = module.get(CreateCalendarEventService); + }); + + afterEach(() => jest.clearAllMocks()); + + it('routes Google accounts to the Google driver', async () => { + const data = composedEvent(ConnectedAccountProvider.GOOGLE); + + await service.createComposedCalendarEvent(data); + + expect(googleCreate).toHaveBeenCalledWith( + data.input, + data.connectedAccount, + ); + expect(microsoftCreate).not.toHaveBeenCalled(); + }); + + it('routes Microsoft accounts to the Microsoft driver', async () => { + const data = composedEvent(ConnectedAccountProvider.MICROSOFT); + + await service.createComposedCalendarEvent(data); + + expect(microsoftCreate).toHaveBeenCalledWith( + data.input, + data.connectedAccount, + ); + expect(googleCreate).not.toHaveBeenCalled(); + }); + + it('routes CalDAV accounts to the CalDav driver', async () => { + const data = composedEvent(ConnectedAccountProvider.IMAP_SMTP_CALDAV); + + await service.createComposedCalendarEvent(data); + + expect(calDavCreate).toHaveBeenCalledWith( + data.input, + data.connectedAccount, + ); + expect(googleCreate).not.toHaveBeenCalled(); + expect(microsoftCreate).not.toHaveBeenCalled(); + }); + + it('throws for unsupported providers', async () => { + await expect( + service.createComposedCalendarEvent( + composedEvent(ConnectedAccountProvider.EMAIL_GROUP), + ), + ).rejects.toThrow(CalendarEventCreationException); + }); + + it('persists the created event through the save service', async () => { + const data = composedEvent(ConnectedAccountProvider.GOOGLE); + + await service.persistCalendarEvent(createdEvent, data, 'workspace-1'); + + expect(saveCalendarEvents).toHaveBeenCalledWith( + [createdEvent], + data.calendarChannel, + data.connectedAccount, + 'workspace-1', + ); + }); + + it('swallows persistence failures so the create still succeeds', async () => { + saveCalendarEvents.mockRejectedValue(new Error('db down')); + + await expect( + service.persistCalendarEvent( + createdEvent, + composedEvent(ConnectedAccountProvider.GOOGLE), + 'workspace-1', + ), + ).resolves.toBeUndefined(); + }); +}); diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/services/calendar-event-composer.service.ts b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/services/calendar-event-composer.service.ts new file mode 100644 index 0000000000..99a24ecad4 --- /dev/null +++ b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/services/calendar-event-composer.service.ts @@ -0,0 +1,272 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; + +import { isNonEmptyString } from '@sniptt/guards'; +import { MAX_EMAIL_RECIPIENTS } from 'twenty-shared/constants'; +import { isDefined, isValidUuid } from 'twenty-shared/utils'; +import { type Repository } from 'typeorm'; +import { z } from 'zod'; + +import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; +import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; +import { getMissingCreateEventScopes } from 'src/modules/calendar/calendar-event-creation-manager/utils/get-missing-create-event-scopes.util'; +import { isCalendarCreationSupportedProvider } from 'src/modules/calendar/calendar-event-creation-manager/utils/is-calendar-creation-supported-provider.util'; +import { isValidTimeZone } from 'src/modules/calendar/calendar-event-creation-manager/utils/is-valid-time-zone.util'; +import { type CalendarEventComposerResult } from 'src/modules/calendar/calendar-event-creation-manager/types/calendar-event-composer-result.type'; +import { type CalendarEventToCreate } from 'src/modules/calendar/calendar-event-creation-manager/types/calendar-event-to-create.type'; +import { type ComposeCalendarEventParams } from 'src/modules/calendar/calendar-event-creation-manager/types/compose-calendar-event-params.type'; + +// Timed events need an absolute instant, so the date-time must carry an explicit +// UTC offset (Z or ±hh:mm); without one the instant is ambiguous and providers +// would schedule it at the wrong time. All-day boundaries are calendar dates. +const offsetDateTimeSchema = z.string().datetime({ offset: true }); +const dateSchema = z.string().date(); + +type ResolvedCalendarAccount = + | { + connectedAccount: ConnectedAccountEntity; + calendarChannel: CalendarChannelEntity; + } + | { error: string }; + +@Injectable() +export class CalendarEventComposerService { + private readonly emailSchema = z.string().trim().pipe(z.email()); + + constructor( + @InjectRepository(ConnectedAccountEntity) + private readonly connectedAccountRepository: Repository, + @InjectRepository(CalendarChannelEntity) + private readonly calendarChannelRepository: Repository, + ) {} + + async composeCalendarEvent( + params: ComposeCalendarEventParams, + workspaceId: string, + ): Promise { + const normalizedInput = this.normalizeAndValidateInput(params); + + if ('error' in normalizedInput) { + return { success: false, error: normalizedInput.error }; + } + + const resolution = await this.resolveCalendarAccount( + params.connectedAccountId, + workspaceId, + ); + + if ('error' in resolution) { + return { success: false, error: resolution.error }; + } + + const { connectedAccount, calendarChannel } = resolution; + + const missingScopes = getMissingCreateEventScopes(connectedAccount); + + if (missingScopes.length > 0) { + return { + success: false, + error: `The connected ${connectedAccount.provider} account is missing calendar permissions (${missingScopes.join(', ')}). Please reconnect the account to grant calendar access.`, + }; + } + + return { + success: true, + data: { input: normalizedInput, connectedAccount, calendarChannel }, + }; + } + + private normalizeAndValidateInput( + params: ComposeCalendarEventParams, + ): CalendarEventToCreate | { error: string } { + const title = params.title?.trim(); + + if (!isNonEmptyString(title)) { + return { error: 'A title is required to create a calendar event' }; + } + + const isFullDay = params.isFullDay ?? false; + + const datesError = this.validateDates( + params.startsAt, + params.endsAt, + isFullDay, + ); + + if (isDefined(datesError)) { + return { error: datesError }; + } + + const timeZone = params.timeZone ?? 'UTC'; + + if (!isValidTimeZone(timeZone)) { + return { error: `timeZone '${timeZone}' is not a valid IANA time zone` }; + } + + const sendInvitations = params.sendInvitations ?? false; + // Attendees are only ever attached when the caller explicitly opts in to + // notifying them, so creating an event never silently emails external people. + const attendeeEmails = sendInvitations + ? this.parseAttendeeEmails(params.attendees) + : []; + + if (attendeeEmails.length > MAX_EMAIL_RECIPIENTS) { + return { + error: `Too many attendees: ${attendeeEmails.length}. Maximum allowed is ${MAX_EMAIL_RECIPIENTS}.`, + }; + } + + const invalidAttendees = attendeeEmails.filter( + (email) => !this.emailSchema.safeParse(email).success, + ); + + if (invalidAttendees.length > 0) { + return { + error: `Invalid attendee email addresses: ${invalidAttendees.join(', ')}`, + }; + } + + return { + title, + description: params.description, + location: params.location, + startsAt: params.startsAt, + endsAt: params.endsAt, + isFullDay, + timeZone, + attendees: attendeeEmails.map((email) => ({ email })), + sendInvitations, + addConferencing: params.addConferencing ?? false, + }; + } + + // All-day boundaries collapse to a date, so they must be validated at day + // granularity; timed boundaries are absolute instants and must carry an offset. + private validateDates( + startsAt: string, + endsAt: string, + isFullDay: boolean, + ): string | undefined { + if (isFullDay) { + const startDate = startsAt.slice(0, 10); + const endDate = endsAt.slice(0, 10); + + if ( + !dateSchema.safeParse(startDate).success || + !dateSchema.safeParse(endDate).success + ) { + return 'startsAt and endsAt must be valid ISO 8601 dates'; + } + + if (endDate <= startDate) { + return 'endsAt must be a later day than startsAt for all-day events'; + } + + return undefined; + } + + if ( + !offsetDateTimeSchema.safeParse(startsAt).success || + !offsetDateTimeSchema.safeParse(endsAt).success + ) { + return 'startsAt and endsAt must be ISO 8601 date-times with an offset (e.g. 2026-07-01T15:00:00Z)'; + } + + if (Date.parse(endsAt) <= Date.parse(startsAt)) { + return 'endsAt must be after startsAt'; + } + + return undefined; + } + + private parseAttendeeEmails(attendees: string | undefined): string[] { + return (attendees ?? '') + .split(',') + .map((email) => email.trim()) + .filter((email) => email.length > 0); + } + + private async resolveCalendarAccount( + connectedAccountId: string | undefined, + workspaceId: string, + ): Promise { + // A blank id (the workflow node's default) falls back to the default account. + if (isNonEmptyString(connectedAccountId)) { + if (!isValidUuid(connectedAccountId)) { + return { error: 'The provided connectedAccountId is not a valid UUID' }; + } + + const connectedAccount = await this.connectedAccountRepository.findOne({ + where: { id: connectedAccountId, workspaceId }, + }); + + if (!isDefined(connectedAccount)) { + return { + error: `No connected account found for id '${connectedAccountId}'`, + }; + } + + if (!isCalendarCreationSupportedProvider(connectedAccount.provider)) { + return { + error: `Calendar event creation is only supported for Google, Microsoft and CalDAV accounts (got ${connectedAccount.provider})`, + }; + } + + const calendarChannel = await this.findSyncEnabledCalendarChannel( + connectedAccount.id, + workspaceId, + ); + + if (!isDefined(calendarChannel)) { + return { + error: `Connected account '${connectedAccountId}' has no calendar channel with sync enabled. Enable calendar sync for this account first.`, + }; + } + + return { connectedAccount, calendarChannel }; + } + + return this.resolveDefaultCalendarAccount(workspaceId); + } + + // Only sync-enabled channels are eligible: a created event is reconciled by the + // provider sync, which skips channels whose sync is disabled. + private async resolveDefaultCalendarAccount( + workspaceId: string, + ): Promise { + const calendarChannels = await this.calendarChannelRepository.find({ + where: { workspaceId, isSyncEnabled: true }, + relations: { connectedAccount: true }, + order: { createdAt: 'ASC' }, + }); + + const calendarChannel = calendarChannels.find( + (channel) => + isDefined(channel.connectedAccount) && + !isDefined(channel.connectedAccount.archivedAt) && + isCalendarCreationSupportedProvider(channel.connectedAccount.provider), + ); + + if (!isDefined(calendarChannel)) { + return { + error: + 'No Google, Microsoft or CalDAV account with calendar sync is connected in this workspace', + }; + } + + return { + connectedAccount: calendarChannel.connectedAccount, + calendarChannel, + }; + } + + private async findSyncEnabledCalendarChannel( + connectedAccountId: string, + workspaceId: string, + ): Promise { + return this.calendarChannelRepository.findOne({ + where: { connectedAccountId, workspaceId, isSyncEnabled: true }, + order: { createdAt: 'ASC' }, + }); + } +} diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/services/create-calendar-event.service.ts b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/services/create-calendar-event.service.ts new file mode 100644 index 0000000000..88371e4f0c --- /dev/null +++ b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/services/create-calendar-event.service.ts @@ -0,0 +1,75 @@ +import { Injectable, Logger } from '@nestjs/common'; + +import { ConnectedAccountProvider } from 'twenty-shared/types'; + +import { CalDavCreateEventService } from 'src/modules/calendar/calendar-event-creation-manager/drivers/caldav/services/caldav-create-event.service'; +import { GoogleCalendarCreateEventService } from 'src/modules/calendar/calendar-event-creation-manager/drivers/google-calendar/services/google-calendar-create-event.service'; +import { MicrosoftCalendarCreateEventService } from 'src/modules/calendar/calendar-event-creation-manager/drivers/microsoft-calendar/services/microsoft-calendar-create-event.service'; +import { + CalendarEventCreationException, + CalendarEventCreationExceptionCode, +} from 'src/modules/calendar/calendar-event-creation-manager/exceptions/calendar-event-creation.exception'; +import { CalendarSaveEventsService } from 'src/modules/calendar/calendar-event-import-manager/services/calendar-save-events.service'; +import { type ComposedCalendarEvent } from 'src/modules/calendar/calendar-event-creation-manager/types/composed-calendar-event.type'; +import { type FetchedCalendarEvent } from 'src/modules/calendar/common/types/fetched-calendar-event'; + +@Injectable() +export class CreateCalendarEventService { + private readonly logger = new Logger(CreateCalendarEventService.name); + + constructor( + private readonly googleCalendarCreateEventService: GoogleCalendarCreateEventService, + private readonly microsoftCalendarCreateEventService: MicrosoftCalendarCreateEventService, + private readonly calDavCreateEventService: CalDavCreateEventService, + private readonly calendarSaveEventsService: CalendarSaveEventsService, + ) {} + + async createComposedCalendarEvent( + data: ComposedCalendarEvent, + ): Promise { + switch (data.connectedAccount.provider) { + case ConnectedAccountProvider.GOOGLE: + return this.googleCalendarCreateEventService.createCalendarEvent( + data.input, + data.connectedAccount, + ); + case ConnectedAccountProvider.MICROSOFT: + return this.microsoftCalendarCreateEventService.createCalendarEvent( + data.input, + data.connectedAccount, + ); + case ConnectedAccountProvider.IMAP_SMTP_CALDAV: + return this.calDavCreateEventService.createCalendarEvent( + data.input, + data.connectedAccount, + ); + default: + throw new CalendarEventCreationException( + `Calendar event creation is not supported for provider ${data.connectedAccount.provider}`, + CalendarEventCreationExceptionCode.PROVIDER_NOT_SUPPORTED, + ); + } + } + + // Persist the created event right away so it is immediately visible in Twenty. + // The next provider sync reconciles it via its external id, so a persistence + // failure here is non-fatal. + async persistCalendarEvent( + createdEvent: FetchedCalendarEvent, + data: ComposedCalendarEvent, + workspaceId: string, + ): Promise { + try { + await this.calendarSaveEventsService.saveCalendarEventsAndEnqueueContactCreationJob( + [createdEvent], + data.calendarChannel, + data.connectedAccount, + workspaceId, + ); + } catch (persistenceError) { + this.logger.warn( + `Failed to persist created calendar event (sync will recover): ${persistenceError}`, + ); + } + } +} diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/types/calendar-event-attendee-to-create.type.ts b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/types/calendar-event-attendee-to-create.type.ts new file mode 100644 index 0000000000..3357dcd23d --- /dev/null +++ b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/types/calendar-event-attendee-to-create.type.ts @@ -0,0 +1,4 @@ +export type CalendarEventAttendeeToCreate = { + email: string; + displayName?: string; +}; diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/types/calendar-event-composer-result.type.ts b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/types/calendar-event-composer-result.type.ts new file mode 100644 index 0000000000..a2bc7609cb --- /dev/null +++ b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/types/calendar-event-composer-result.type.ts @@ -0,0 +1,5 @@ +import { type ComposedCalendarEvent } from 'src/modules/calendar/calendar-event-creation-manager/types/composed-calendar-event.type'; + +export type CalendarEventComposerResult = + | { success: true; data: ComposedCalendarEvent } + | { success: false; error: string }; diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/types/calendar-event-to-create.type.ts b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/types/calendar-event-to-create.type.ts new file mode 100644 index 0000000000..00d6b2e095 --- /dev/null +++ b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/types/calendar-event-to-create.type.ts @@ -0,0 +1,14 @@ +import { type CalendarEventAttendeeToCreate } from 'src/modules/calendar/calendar-event-creation-manager/types/calendar-event-attendee-to-create.type'; + +export type CalendarEventToCreate = { + title: string; + description?: string; + location?: string; + startsAt: string; + endsAt: string; + isFullDay: boolean; + timeZone: string; + attendees: CalendarEventAttendeeToCreate[]; + sendInvitations: boolean; + addConferencing: boolean; +}; diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/types/compose-calendar-event-params.type.ts b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/types/compose-calendar-event-params.type.ts new file mode 100644 index 0000000000..81d83cff65 --- /dev/null +++ b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/types/compose-calendar-event-params.type.ts @@ -0,0 +1,13 @@ +export type ComposeCalendarEventParams = { + title: string; + description?: string; + location?: string; + startsAt: string; + endsAt: string; + isFullDay?: boolean; + timeZone?: string; + attendees?: string; + sendInvitations?: boolean; + addConferencing?: boolean; + connectedAccountId?: string; +}; diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/types/composed-calendar-event.type.ts b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/types/composed-calendar-event.type.ts new file mode 100644 index 0000000000..ac4242eec4 --- /dev/null +++ b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/types/composed-calendar-event.type.ts @@ -0,0 +1,9 @@ +import { type CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; +import { type CalendarEventToCreate } from 'src/modules/calendar/calendar-event-creation-manager/types/calendar-event-to-create.type'; + +export type ComposedCalendarEvent = { + input: CalendarEventToCreate; + connectedAccount: ConnectedAccountEntity; + calendarChannel: CalendarChannelEntity; +}; diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/utils/__tests__/get-missing-create-event-scopes.util.spec.ts b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/utils/__tests__/get-missing-create-event-scopes.util.spec.ts new file mode 100644 index 0000000000..1c8e2d1322 --- /dev/null +++ b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/utils/__tests__/get-missing-create-event-scopes.util.spec.ts @@ -0,0 +1,62 @@ +import { ConnectedAccountProvider } from 'twenty-shared/types'; + +import { getMissingCreateEventScopes } from 'src/modules/calendar/calendar-event-creation-manager/utils/get-missing-create-event-scopes.util'; + +const GOOGLE_SCOPE = 'https://www.googleapis.com/auth/calendar.events'; +const MICROSOFT_SCOPE = 'Calendars.ReadWrite'; + +describe('getMissingCreateEventScopes', () => { + it('returns no missing scope when Google has calendar.events', () => { + expect( + getMissingCreateEventScopes({ + provider: ConnectedAccountProvider.GOOGLE, + scopes: ['email', GOOGLE_SCOPE], + }), + ).toEqual([]); + }); + + it('reports the Google calendar.events scope when missing', () => { + expect( + getMissingCreateEventScopes({ + provider: ConnectedAccountProvider.GOOGLE, + scopes: ['email'], + }), + ).toEqual([GOOGLE_SCOPE]); + }); + + it('returns no missing scope when Microsoft has Calendars.ReadWrite', () => { + expect( + getMissingCreateEventScopes({ + provider: ConnectedAccountProvider.MICROSOFT, + scopes: [MICROSOFT_SCOPE], + }), + ).toEqual([]); + }); + + it('reports the Microsoft Calendars.ReadWrite scope when missing', () => { + expect( + getMissingCreateEventScopes({ + provider: ConnectedAccountProvider.MICROSOFT, + scopes: ['Calendars.Read'], + }), + ).toEqual([MICROSOFT_SCOPE]); + }); + + it('treats null scopes as missing', () => { + expect( + getMissingCreateEventScopes({ + provider: ConnectedAccountProvider.GOOGLE, + scopes: null, + }), + ).toEqual([GOOGLE_SCOPE]); + }); + + it('does not require OAuth scopes for non-OAuth providers', () => { + expect( + getMissingCreateEventScopes({ + provider: ConnectedAccountProvider.IMAP_SMTP_CALDAV, + scopes: null, + }), + ).toEqual([]); + }); +}); diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/utils/get-missing-create-event-scopes.util.ts b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/utils/get-missing-create-event-scopes.util.ts new file mode 100644 index 0000000000..4b40cc646d --- /dev/null +++ b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/utils/get-missing-create-event-scopes.util.ts @@ -0,0 +1,41 @@ +import { ConnectedAccountProvider } from 'twenty-shared/types'; +import { assertUnreachable, isDefined } from 'twenty-shared/utils'; + +const GOOGLE_CALENDAR_EVENTS_SCOPE = + 'https://www.googleapis.com/auth/calendar.events'; +const MICROSOFT_CALENDARS_READ_WRITE_SCOPE = 'Calendars.ReadWrite'; + +export const getMissingCreateEventScopes = (connectedAccount: { + provider: ConnectedAccountProvider; + scopes: string[] | null; +}): string[] => { + const scopes = connectedAccount.scopes; + + switch (connectedAccount.provider) { + case ConnectedAccountProvider.GOOGLE: { + const hasScope = + isDefined(scopes) && scopes.includes(GOOGLE_CALENDAR_EVENTS_SCOPE); + + return hasScope ? [] : [GOOGLE_CALENDAR_EVENTS_SCOPE]; + } + case ConnectedAccountProvider.MICROSOFT: { + const hasScope = + isDefined(scopes) && + scopes.includes(MICROSOFT_CALENDARS_READ_WRITE_SCOPE); + + return hasScope ? [] : [MICROSOFT_CALENDARS_READ_WRITE_SCOPE]; + } + // Non-OAuth providers do not rely on OAuth scopes to create events. + case ConnectedAccountProvider.IMAP_SMTP_CALDAV: + case ConnectedAccountProvider.EMAIL_GROUP: + case ConnectedAccountProvider.APP: + case ConnectedAccountProvider.OIDC: + case ConnectedAccountProvider.SAML: + return []; + default: + return assertUnreachable( + connectedAccount.provider, + `Unhandled connected account provider for create event scopes: ${connectedAccount.provider}`, + ); + } +}; diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/utils/is-calendar-creation-supported-provider.util.ts b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/utils/is-calendar-creation-supported-provider.util.ts new file mode 100644 index 0000000000..1a4fe1e422 --- /dev/null +++ b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/utils/is-calendar-creation-supported-provider.util.ts @@ -0,0 +1,8 @@ +import { ConnectedAccountProvider } from 'twenty-shared/types'; + +export const isCalendarCreationSupportedProvider = ( + provider: ConnectedAccountProvider, +): boolean => + provider === ConnectedAccountProvider.GOOGLE || + provider === ConnectedAccountProvider.MICROSOFT || + provider === ConnectedAccountProvider.IMAP_SMTP_CALDAV; diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/utils/is-valid-time-zone.util.ts b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/utils/is-valid-time-zone.util.ts new file mode 100644 index 0000000000..c9a29aa5c6 --- /dev/null +++ b/packages/twenty-server/src/modules/calendar/calendar-event-creation-manager/utils/is-valid-time-zone.util.ts @@ -0,0 +1,9 @@ +export const isValidTimeZone = (timeZone: string): boolean => { + try { + new Intl.DateTimeFormat('en-US', { timeZone }); + + return true; + } catch { + return false; + } +}; diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/calendar-event-import-manager.module.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/calendar-event-import-manager.module.ts index 66c9914dfc..ac7c04d71d 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/calendar-event-import-manager.module.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/calendar-event-import-manager.module.ts @@ -93,6 +93,7 @@ import { RefreshTokensManagerModule } from 'src/modules/connected-account/refres exports: [ CalendarEventsImportService, CalendarFetchEventsService, + CalendarSaveEventsService, CalendarEventListFetchCronCommand, CalendarEventsImportCronCommand, CalendarOngoingStaleCronCommand, diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-operations.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-operations.workspace-service.ts index 515c7d22c2..6c2b9b1e77 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-operations.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-operations.workspace-service.ts @@ -311,6 +311,31 @@ export class WorkflowVersionStepOperationsWorkspaceService { }, }; } + case WorkflowActionType.CREATE_CALENDAR_EVENT: { + return { + builtStep: { + ...baseStep, + name: 'Create Calendar Event', + type: WorkflowActionType.CREATE_CALENDAR_EVENT, + settings: { + ...BASE_STEP_DEFINITION, + input: { + connectedAccountId: '', + title: '', + description: '', + location: '', + startsAt: '', + endsAt: '', + isFullDay: false, + timeZone: '', + attendees: '', + sendInvitations: false, + addConferencing: false, + }, + }, + }, + }; + } case WorkflowActionType.DRAFT_EMAIL: { return { builtStep: { diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/factories/workflow-action.factory.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/factories/workflow-action.factory.ts index e97bee8510..ed155c0cb9 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/factories/workflow-action.factory.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/factories/workflow-action.factory.ts @@ -14,6 +14,7 @@ import { FilterWorkflowAction } from 'src/modules/workflow/workflow-executor/wor import { FormWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/form/form.workflow-action'; import { HttpRequestWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/http-request/http-request.workflow-action'; import { IfElseWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/if-else/if-else.workflow-action'; +import { CreateCalendarEventWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/create-calendar-event/create-calendar-event.workflow-action'; import { IteratorWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/iterator/iterator.workflow-action'; import { LogicFunctionWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/logic-function/logic-function.workflow-action'; import { DraftEmailWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/mail-sender/draft-email.workflow-action'; @@ -44,6 +45,7 @@ export class WorkflowActionFactory { private readonly httpRequestWorkflowAction: HttpRequestWorkflowAction, private readonly sendEmailWorkflowAction: SendEmailWorkflowAction, private readonly draftEmailWorkflowAction: DraftEmailWorkflowAction, + private readonly createCalendarEventWorkflowAction: CreateCalendarEventWorkflowAction, private readonly aiAgentWorkflowAction: AiAgentWorkflowAction, private readonly emptyWorkflowAction: EmptyWorkflowAction, private readonly delayWorkflowAction: DelayWorkflowAction, @@ -59,6 +61,8 @@ export class WorkflowActionFactory { return this.sendEmailWorkflowAction; case WorkflowActionType.DRAFT_EMAIL: return this.draftEmailWorkflowAction; + case WorkflowActionType.CREATE_CALENDAR_EVENT: + return this.createCalendarEventWorkflowAction; case WorkflowActionType.CREATE_RECORD: return this.createRecordWorkflowAction; case WorkflowActionType.UPSERT_RECORD: diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/create-calendar-event/create-calendar-event-action.module.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/create-calendar-event/create-calendar-event-action.module.ts new file mode 100644 index 0000000000..9c05c7c831 --- /dev/null +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/create-calendar-event/create-calendar-event-action.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; + +import { ToolModule } from 'src/engine/core-modules/tool/tool.module'; +import { CreateCalendarEventWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/create-calendar-event/create-calendar-event.workflow-action'; +import { WorkflowRunModule } from 'src/modules/workflow/workflow-runner/workflow-run/workflow-run.module'; + +@Module({ + imports: [ToolModule, WorkflowRunModule], + providers: [CreateCalendarEventWorkflowAction], + exports: [CreateCalendarEventWorkflowAction], +}) +export class CreateCalendarEventActionModule {} diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/create-calendar-event/create-calendar-event.workflow-action.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/create-calendar-event/create-calendar-event.workflow-action.ts new file mode 100644 index 0000000000..2ce5156db6 --- /dev/null +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/create-calendar-event/create-calendar-event.workflow-action.ts @@ -0,0 +1,52 @@ +import { Injectable } from '@nestjs/common'; + +import { type WorkflowRunStepLog } from 'twenty-shared/workflow'; + +import { CreateCalendarEventTool } from 'src/engine/core-modules/tool/tools/calendar-tool/create-calendar-event-tool'; +import { type ToolOutput } from 'src/engine/core-modules/tool/types/tool-output.type'; +import { type Tool } from 'src/engine/core-modules/tool/types/tool.type'; +import { + WorkflowStepExecutorException, + WorkflowStepExecutorExceptionCode, +} from 'src/modules/workflow/workflow-executor/exceptions/workflow-step-executor.exception'; +import { isWorkflowCreateCalendarEventAction } from 'src/modules/workflow/workflow-executor/workflow-actions/create-calendar-event/guards/is-workflow-create-calendar-event-action.guard'; +import { type WorkflowCreateCalendarEventActionInput } from 'src/modules/workflow/workflow-executor/workflow-actions/create-calendar-event/types/workflow-create-calendar-event-action-input.type'; +import { buildCreateCalendarEventStepLog } from 'src/modules/workflow/workflow-executor/workflow-actions/create-calendar-event/utils/build-create-calendar-event-step-log.util'; +import { ToolBackedWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/tool-backed/tool-backed.workflow-action'; +import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type'; +import { WorkflowRunStepLogWorkspaceService } from 'src/modules/workflow/workflow-runner/workflow-run/workflow-run-step-log.workspace-service'; + +@Injectable() +export class CreateCalendarEventWorkflowAction extends ToolBackedWorkflowAction { + constructor( + private readonly createCalendarEventTool: CreateCalendarEventTool, + workflowRunStepLogService: WorkflowRunStepLogWorkspaceService, + ) { + super(CreateCalendarEventWorkflowAction.name, workflowRunStepLogService); + } + + protected getTool(): Tool { + return this.createCalendarEventTool; + } + + protected assertStep(step: WorkflowAction): void { + if (!isWorkflowCreateCalendarEventAction(step)) { + throw new WorkflowStepExecutorException( + 'Step is not a create-calendar-event action', + WorkflowStepExecutorExceptionCode.INVALID_STEP_TYPE, + ); + } + } + + protected buildStepLog({ + input, + output, + durationMs, + }: { + input: WorkflowCreateCalendarEventActionInput; + output: ToolOutput; + durationMs: number; + }): WorkflowRunStepLog { + return buildCreateCalendarEventStepLog({ input, output, durationMs }); + } +} diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/create-calendar-event/guards/is-workflow-create-calendar-event-action.guard.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/create-calendar-event/guards/is-workflow-create-calendar-event-action.guard.ts new file mode 100644 index 0000000000..85ac25fb6a --- /dev/null +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/create-calendar-event/guards/is-workflow-create-calendar-event-action.guard.ts @@ -0,0 +1,12 @@ +import { WorkflowActionType } from 'twenty-shared/workflow'; + +import { + type WorkflowAction, + type WorkflowCreateCalendarEventAction, +} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type'; + +export const isWorkflowCreateCalendarEventAction = ( + action: WorkflowAction, +): action is WorkflowCreateCalendarEventAction => { + return action.type === WorkflowActionType.CREATE_CALENDAR_EVENT; +}; diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/create-calendar-event/types/workflow-create-calendar-event-action-input.type.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/create-calendar-event/types/workflow-create-calendar-event-action-input.type.ts new file mode 100644 index 0000000000..db9f129307 --- /dev/null +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/create-calendar-event/types/workflow-create-calendar-event-action-input.type.ts @@ -0,0 +1,13 @@ +export type WorkflowCreateCalendarEventActionInput = { + connectedAccountId: string; + title: string; + description?: string; + location?: string; + startsAt: string; + endsAt: string; + isFullDay: boolean; + timeZone?: string; + attendees?: string; + sendInvitations: boolean; + addConferencing: boolean; +}; diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/create-calendar-event/types/workflow-create-calendar-event-action-settings.type.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/create-calendar-event/types/workflow-create-calendar-event-action-settings.type.ts new file mode 100644 index 0000000000..c713517d0f --- /dev/null +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/create-calendar-event/types/workflow-create-calendar-event-action-settings.type.ts @@ -0,0 +1,8 @@ +import { type BaseWorkflowActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-settings.type'; + +import { type WorkflowCreateCalendarEventActionInput } from './workflow-create-calendar-event-action-input.type'; + +export type WorkflowCreateCalendarEventActionSettings = + BaseWorkflowActionSettings & { + input: WorkflowCreateCalendarEventActionInput; + }; diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/create-calendar-event/utils/__tests__/build-create-calendar-event-step-log.util.spec.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/create-calendar-event/utils/__tests__/build-create-calendar-event-step-log.util.spec.ts new file mode 100644 index 0000000000..ac1f316cdb --- /dev/null +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/create-calendar-event/utils/__tests__/build-create-calendar-event-step-log.util.spec.ts @@ -0,0 +1,69 @@ +import { type ToolOutput } from 'src/engine/core-modules/tool/types/tool-output.type'; +import { type WorkflowCreateCalendarEventActionInput } from 'src/modules/workflow/workflow-executor/workflow-actions/create-calendar-event/types/workflow-create-calendar-event-action-input.type'; +import { buildCreateCalendarEventStepLog } from 'src/modules/workflow/workflow-executor/workflow-actions/create-calendar-event/utils/build-create-calendar-event-step-log.util'; + +const input: WorkflowCreateCalendarEventActionInput = { + connectedAccountId: 'account-1', + title: 'Sync', + startsAt: '2026-07-01T14:00:00Z', + endsAt: '2026-07-01T15:00:00Z', + isFullDay: false, + sendInvitations: false, + addConferencing: false, +}; + +describe('buildCreateCalendarEventStepLog', () => { + it('builds a success log from the tool result', () => { + const output: ToolOutput = { + success: true, + message: 'Calendar event "Sync" created', + result: { + iCalUid: 'uid-1', + title: 'Sync', + startsAt: '2026-07-01T14:00:00Z', + endsAt: '2026-07-01T15:00:00Z', + conferenceLink: 'https://meet.google.com/abc', + attendeeCount: 2, + connectedAccountId: 'resolved-account', + }, + }; + + const log = buildCreateCalendarEventStepLog({ + input, + output, + durationMs: 12, + }); + + expect(log.details).toMatchObject({ + type: 'CREATE_CALENDAR_EVENT', + status: 'SUCCESS', + iCalUid: 'uid-1', + conferenceLink: 'https://meet.google.com/abc', + attendeeCount: 2, + connectedAccountId: 'resolved-account', + durationMs: 12, + }); + }); + + it('builds an error log and falls back to the input fields', () => { + const output: ToolOutput = { + success: false, + message: 'Failed to create calendar event', + error: 'boom', + }; + + const log = buildCreateCalendarEventStepLog({ + input, + output, + durationMs: 5, + }); + + expect(log.details).toMatchObject({ + type: 'CREATE_CALENDAR_EVENT', + status: 'ERROR', + title: 'Sync', + connectedAccountId: 'account-1', + error: 'boom', + }); + }); +}); diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/create-calendar-event/utils/build-create-calendar-event-step-log.util.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/create-calendar-event/utils/build-create-calendar-event-step-log.util.ts new file mode 100644 index 0000000000..d07c42464a --- /dev/null +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/create-calendar-event/utils/build-create-calendar-event-step-log.util.ts @@ -0,0 +1,41 @@ +import { type WorkflowRunStepLog } from 'twenty-shared/workflow'; + +import { type ToolOutput } from 'src/engine/core-modules/tool/types/tool-output.type'; +import { type WorkflowCreateCalendarEventActionInput } from 'src/modules/workflow/workflow-executor/workflow-actions/create-calendar-event/types/workflow-create-calendar-event-action-input.type'; + +export const buildCreateCalendarEventStepLog = ({ + input, + output, + durationMs, +}: { + input: WorkflowCreateCalendarEventActionInput; + output: ToolOutput; + durationMs: number; +}): WorkflowRunStepLog => { + const result = (output.result ?? {}) as Record; + + const extractString = (key: string): string | undefined => + typeof result[key] === 'string' ? result[key] : undefined; + + const extractNumber = (key: string): number | undefined => + typeof result[key] === 'number' ? result[key] : undefined; + + return { + details: { + type: 'CREATE_CALENDAR_EVENT', + status: output.success ? 'SUCCESS' : 'ERROR', + title: extractString('title') ?? input.title, + startsAt: extractString('startsAt') ?? input.startsAt, + endsAt: extractString('endsAt') ?? input.endsAt, + attendeeCount: extractNumber('attendeeCount'), + conferenceLink: extractString('conferenceLink'), + connectedAccountId: + extractString('connectedAccountId') ?? input.connectedAccountId, + iCalUid: extractString('iCalUid'), + error: output.error, + durationMs, + }, + entries: [], + sizeBytes: 0, + }; +}; diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-settings.type.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-settings.type.ts index ff84995c32..d2a1288ef3 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-settings.type.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-settings.type.ts @@ -1,6 +1,7 @@ import { type OutputSchema } from 'src/modules/workflow/workflow-builder/workflow-schema/types/output-schema.type'; import { type WorkflowAiAgentActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/ai-agent/types/workflow-ai-agent-action-settings.type'; import { type WorkflowCodeActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/code/types/workflow-code-action-settings.type'; +import { type WorkflowCreateCalendarEventActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/create-calendar-event/types/workflow-create-calendar-event-action-settings.type'; import { type WorkflowDelayActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/delay/types/workflow-delay-action-settings.type'; import { type WorkflowFilterActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/filter/types/workflow-filter-action-settings.type'; import { type WorkflowFormActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/form/types/workflow-form-action-settings.type'; @@ -37,6 +38,7 @@ export type WithExpectedOutputSchema = { export type WorkflowActionSettings = | WorkflowLogicFunctionActionSettings | WorkflowSendEmailActionSettings + | WorkflowCreateCalendarEventActionSettings | WorkflowCodeActionSettings | WorkflowCreateRecordActionSettings | WorkflowUpdateRecordActionSettings diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type.ts index 93528086ef..ce4ce93aac 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type.ts @@ -2,6 +2,7 @@ import { WorkflowActionType } from 'twenty-shared/workflow'; import { type WorkflowAiAgentActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/ai-agent/types/workflow-ai-agent-action-settings.type'; import { type WorkflowCodeActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/code/types/workflow-code-action-settings.type'; +import { type WorkflowCreateCalendarEventActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/create-calendar-event/types/workflow-create-calendar-event-action-settings.type'; import { type WorkflowDelayActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/delay/types/workflow-delay-action-settings.type'; import { type WorkflowFilterActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/filter/types/workflow-filter-action-settings.type'; import { type WorkflowFormActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/form/types/workflow-form-action-settings.type'; @@ -53,6 +54,11 @@ export type WorkflowDraftEmailAction = BaseWorkflowAction & { settings: WorkflowSendEmailActionSettings; }; +export type WorkflowCreateCalendarEventAction = BaseWorkflowAction & { + type: WorkflowActionType.CREATE_CALENDAR_EVENT; + settings: WorkflowCreateCalendarEventActionSettings; +}; + export type WorkflowCreateRecordAction = BaseWorkflowAction & { type: WorkflowActionType.CREATE_RECORD; settings: WorkflowCreateRecordActionSettings; @@ -127,6 +133,7 @@ export type WorkflowAction = | WorkflowLogicFunctionAction | WorkflowSendEmailAction | WorkflowDraftEmailAction + | WorkflowCreateCalendarEventAction | WorkflowCreateRecordAction | WorkflowUpdateRecordAction | WorkflowDeleteRecordAction diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-executor.module.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-executor.module.ts index f89d8becf0..8a69de11ac 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-executor.module.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-executor.module.ts @@ -8,6 +8,7 @@ import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-commo import { WorkflowActionFactory } from 'src/modules/workflow/workflow-executor/factories/workflow-action.factory'; import { AiAgentActionModule } from 'src/modules/workflow/workflow-executor/workflow-actions/ai-agent/ai-agent-action.module'; import { CodeActionModule } from 'src/modules/workflow/workflow-executor/workflow-actions/code/code-action.module'; +import { CreateCalendarEventActionModule } from 'src/modules/workflow/workflow-executor/workflow-actions/create-calendar-event/create-calendar-event-action.module'; import { DelayActionModule } from 'src/modules/workflow/workflow-executor/workflow-actions/delay/delay-action.module'; import { EmptyActionModule } from 'src/modules/workflow/workflow-executor/workflow-actions/empty/empty-action.module'; import { FilterActionModule } from 'src/modules/workflow/workflow-executor/workflow-actions/filter/filter-action.module'; @@ -40,6 +41,7 @@ import { WorkflowRunModule } from 'src/modules/workflow/workflow-runner/workflow FeatureFlagModule, HttpRequestActionModule, MailSenderActionModule, + CreateCalendarEventActionModule, MetricsModule, ], providers: [WorkflowExecutorWorkspaceService, WorkflowActionFactory], diff --git a/packages/twenty-server/test/integration/graphql/suites/create-calendar-event.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/create-calendar-event.integration-spec.ts new file mode 100644 index 0000000000..e4ffcefbc1 --- /dev/null +++ b/packages/twenty-server/test/integration/graphql/suites/create-calendar-event.integration-spec.ts @@ -0,0 +1,46 @@ +import gql from 'graphql-tag'; +import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util'; + +const NON_EXISTENT_CONNECTED_ACCOUNT_ID = + '00000000-0000-4000-8000-000000000000'; + +const CREATE_CALENDAR_EVENT_MUTATION = gql` + mutation CreateCalendarEvent($input: CreateCalendarEventInput!) { + createCalendarEvent(input: $input) { + success + error + iCalUid + conferenceLink + } + } +`; + +// Creating an actual event hits Google/Microsoft and cannot be integration-tested +// without real OAuth + provider mocking. The auth + ownership + validation paths, +// however, fail closed before any provider call — so we assert the mutation is +// wired into the metadata schema and returns a structured failure (never a 500) +// for an account that does not exist. +describe('createCalendarEvent (metadata API) (e2e)', () => { + it('fails closed with a structured error when the connected account does not exist', async () => { + const response = await makeMetadataAPIRequest({ + query: CREATE_CALENDAR_EVENT_MUTATION, + variables: { + input: { + connectedAccountId: NON_EXISTENT_CONNECTED_ACCOUNT_ID, + title: 'Integration test event', + startsAt: '2026-07-01T15:00:00Z', + endsAt: '2026-07-01T16:00:00Z', + }, + }, + }); + + expect(response.body.errors).toBeUndefined(); + + const result = response.body.data.createCalendarEvent; + + expect(result.success).toBe(false); + expect(result.error).toContain('not found'); + expect(result.iCalUid).toBeNull(); + expect(result.conferenceLink).toBeNull(); + }); +}); diff --git a/packages/twenty-shared/src/constants/PermissionFlagType.ts b/packages/twenty-shared/src/constants/PermissionFlagType.ts index c7caf120b0..6234fd0cfb 100644 --- a/packages/twenty-shared/src/constants/PermissionFlagType.ts +++ b/packages/twenty-shared/src/constants/PermissionFlagType.ts @@ -21,6 +21,7 @@ export enum PermissionFlagType { UPLOAD_FILE = 'UPLOAD_FILE', DOWNLOAD_FILE = 'DOWNLOAD_FILE', SEND_EMAIL_TOOL = 'SEND_EMAIL_TOOL', + CREATE_CALENDAR_EVENT_TOOL = 'CREATE_CALENDAR_EVENT_TOOL', HTTP_REQUEST_TOOL = 'HTTP_REQUEST_TOOL', CODE_INTERPRETER_TOOL = 'CODE_INTERPRETER_TOOL', IMPORT_CSV = 'IMPORT_CSV', diff --git a/packages/twenty-shared/src/constants/SystemPermissionFlag.ts b/packages/twenty-shared/src/constants/SystemPermissionFlag.ts index 3ecec52dd5..8fd834c5bf 100644 --- a/packages/twenty-shared/src/constants/SystemPermissionFlag.ts +++ b/packages/twenty-shared/src/constants/SystemPermissionFlag.ts @@ -20,6 +20,7 @@ export const SystemPermissionFlag = { UPLOAD_FILE: 'dc6931a9-44bd-5164-a983-3db5db837f54', DOWNLOAD_FILE: '6edf252f-228e-5e2f-9706-7bab7a796fd3', SEND_EMAIL_TOOL: '19f382a8-689d-5792-a18c-737f0ce9025f', + CREATE_CALENDAR_EVENT_TOOL: '5b8c104e-24d3-4e89-986b-1d9208df6cf4', HTTP_REQUEST_TOOL: '1bf49cf1-bb8f-5eb9-809a-5f34ea79fc6a', CODE_INTERPRETER_TOOL: '11f31c3a-7042-54b7-a2dd-ae31dd9e9fa2', IMPORT_CSV: 'c4912536-a4db-5af9-85ac-be2919c24c5e', diff --git a/packages/twenty-shared/src/workflow/index.ts b/packages/twenty-shared/src/workflow/index.ts index ae24b301a4..8c3665f109 100644 --- a/packages/twenty-shared/src/workflow/index.ts +++ b/packages/twenty-shared/src/workflow/index.ts @@ -35,6 +35,8 @@ export { baseWorkflowActionSchema } from './schemas/base-workflow-action-schema' export { baseWorkflowActionSettingsSchema } from './schemas/base-workflow-action-settings-schema'; export { workflowCodeActionSchema } from './schemas/code-action-schema'; export { workflowCodeActionSettingsSchema } from './schemas/code-action-settings-schema'; +export { workflowCreateCalendarEventActionSchema } from './schemas/create-calendar-event-action-schema'; +export { workflowCreateCalendarEventActionSettingsSchema } from './schemas/create-calendar-event-action-settings-schema'; export { workflowCreateRecordActionSchema } from './schemas/create-record-action-schema'; export { workflowCreateRecordActionSettingsSchema } from './schemas/create-record-action-settings-schema'; export { workflowCronTriggerSchema } from './schemas/cron-trigger-schema'; diff --git a/packages/twenty-shared/src/workflow/schemas/create-calendar-event-action-schema.ts b/packages/twenty-shared/src/workflow/schemas/create-calendar-event-action-schema.ts new file mode 100644 index 0000000000..f58e7989d2 --- /dev/null +++ b/packages/twenty-shared/src/workflow/schemas/create-calendar-event-action-schema.ts @@ -0,0 +1,9 @@ +import { z } from 'zod'; +import { baseWorkflowActionSchema } from './base-workflow-action-schema'; +import { workflowCreateCalendarEventActionSettingsSchema } from './create-calendar-event-action-settings-schema'; + +export const workflowCreateCalendarEventActionSchema = + baseWorkflowActionSchema.extend({ + type: z.literal('CREATE_CALENDAR_EVENT'), + settings: workflowCreateCalendarEventActionSettingsSchema, + }); diff --git a/packages/twenty-shared/src/workflow/schemas/create-calendar-event-action-settings-schema.ts b/packages/twenty-shared/src/workflow/schemas/create-calendar-event-action-settings-schema.ts new file mode 100644 index 0000000000..fdf59a077b --- /dev/null +++ b/packages/twenty-shared/src/workflow/schemas/create-calendar-event-action-settings-schema.ts @@ -0,0 +1,19 @@ +import { z } from 'zod'; +import { baseWorkflowActionSettingsSchema } from './base-workflow-action-settings-schema'; + +export const workflowCreateCalendarEventActionSettingsSchema = + baseWorkflowActionSettingsSchema.extend({ + input: z.object({ + connectedAccountId: z.string(), + title: z.string(), + description: z.string().optional(), + location: z.string().optional(), + startsAt: z.string(), + endsAt: z.string(), + isFullDay: z.boolean(), + timeZone: z.string().optional(), + attendees: z.string().optional().default(''), + sendInvitations: z.boolean(), + addConferencing: z.boolean(), + }), + }); diff --git a/packages/twenty-shared/src/workflow/schemas/workflow-action-schema.ts b/packages/twenty-shared/src/workflow/schemas/workflow-action-schema.ts index 8d79a234b1..7a1077eb4b 100644 --- a/packages/twenty-shared/src/workflow/schemas/workflow-action-schema.ts +++ b/packages/twenty-shared/src/workflow/schemas/workflow-action-schema.ts @@ -1,6 +1,7 @@ import { z } from 'zod'; import { workflowAiAgentActionSchema } from './ai-agent-action-schema'; import { workflowCodeActionSchema } from './code-action-schema'; +import { workflowCreateCalendarEventActionSchema } from './create-calendar-event-action-schema'; import { workflowCreateRecordActionSchema } from './create-record-action-schema'; import { workflowDeleteRecordActionSchema } from './delete-record-action-schema'; import { workflowDraftEmailActionSchema } from './draft-email-action-schema'; @@ -23,6 +24,7 @@ export const workflowActionSchema = z.discriminatedUnion('type', [ workflowLogicFunctionActionSchema, workflowSendEmailActionSchema, workflowDraftEmailActionSchema, + workflowCreateCalendarEventActionSchema, workflowCreateRecordActionSchema, workflowUpdateRecordActionSchema, workflowDeleteRecordActionSchema, diff --git a/packages/twenty-shared/src/workflow/schemas/workflow-run-step-log-schema.ts b/packages/twenty-shared/src/workflow/schemas/workflow-run-step-log-schema.ts index 702b28b89c..9b2f89f775 100644 --- a/packages/twenty-shared/src/workflow/schemas/workflow-run-step-log-schema.ts +++ b/packages/twenty-shared/src/workflow/schemas/workflow-run-step-log-schema.ts @@ -96,11 +96,26 @@ const emailStepLogDetailsSchema = z.object({ durationMs: z.number(), }); +const createCalendarEventStepLogDetailsSchema = z.object({ + type: z.literal('CREATE_CALENDAR_EVENT'), + status: z.enum(['SUCCESS', 'ERROR']), + title: z.string().optional(), + startsAt: z.string().optional(), + endsAt: z.string().optional(), + attendeeCount: z.number().optional(), + conferenceLink: z.string().optional(), + connectedAccountId: z.string().optional(), + iCalUid: z.string().optional(), + error: z.string().optional(), + durationMs: z.number(), +}); + const stepLogDetailsSchema = z.discriminatedUnion('type', [ aiAgentStepLogDetailsSchema, codeStepLogDetailsSchema, httpRequestStepLogDetailsSchema, emailStepLogDetailsSchema, + createCalendarEventStepLogDetailsSchema, ]); export const workflowRunStepLogSchema = z.object({ diff --git a/packages/twenty-shared/src/workflow/types/WorkflowActionType.ts b/packages/twenty-shared/src/workflow/types/WorkflowActionType.ts index 840239d487..358bf103ff 100644 --- a/packages/twenty-shared/src/workflow/types/WorkflowActionType.ts +++ b/packages/twenty-shared/src/workflow/types/WorkflowActionType.ts @@ -3,6 +3,7 @@ export enum WorkflowActionType { LOGIC_FUNCTION = 'LOGIC_FUNCTION', SEND_EMAIL = 'SEND_EMAIL', DRAFT_EMAIL = 'DRAFT_EMAIL', + CREATE_CALENDAR_EVENT = 'CREATE_CALENDAR_EVENT', CREATE_RECORD = 'CREATE_RECORD', UPDATE_RECORD = 'UPDATE_RECORD', DELETE_RECORD = 'DELETE_RECORD', diff --git a/yarn.lock b/yarn.lock index e1cf562bc1..ef00531951 100644 --- a/yarn.lock +++ b/yarn.lock @@ -36048,6 +36048,41 @@ __metadata: languageName: node linkType: hard +"ical-generator@npm:^11.0.0": + version: 11.0.0 + resolution: "ical-generator@npm:11.0.0" + peerDependencies: + "@touch4it/ical-timezones": ">=1.6.0" + "@types/luxon": ">= 1.26.0" + "@types/mocha": ">= 8.2.1" + dayjs: ">= 1.10.0" + luxon: ">= 1.26.0" + moment: ">= 2.29.0" + moment-timezone: ">= 0.5.33" + rrule: ">= 2.6.8" + peerDependenciesMeta: + "@touch4it/ical-timezones": + optional: true + "@types/luxon": + optional: true + "@types/mocha": + optional: true + "@types/node": + optional: true + dayjs: + optional: true + luxon: + optional: true + moment: + optional: true + moment-timezone: + optional: true + rrule: + optional: true + checksum: 10c0/66bef957e01355ad59ffa060da9086a21f499d5dc79f0a254203a79613c891f880dff33e089af011af6ddb1e4d9af95cba86e7bc5c4001064d3333566469d5d7 + languageName: node + linkType: hard + "ico-endec@npm:*": version: 0.1.6 resolution: "ico-endec@npm:0.1.6" @@ -53142,6 +53177,7 @@ __metadata: graphql-upload: "npm:16.0.2" graphql-yoga: "npm:4.0.5" html-to-text: "npm:^9.0.5" + ical-generator: "npm:^11.0.0" imapflow: "npm:1.4.2" ioredis: "npm:5.10.1" jest: "npm:29.7.0"