From 50ea560e57c918477c8c2913c9b83f84b6bdfb85 Mon Sep 17 00:00:00 2001 From: Thomas des Francs Date: Fri, 27 Mar 2026 13:47:07 +0100 Subject: [PATCH] Seed company workflow for email upserts (#18909) @thomtrp --------- Co-authored-by: Charles Bochet --- .../src/metadata/generated/schema.graphql | 10 +- .../src/metadata/generated/schema.ts | 42 +- .../src/metadata/generated/types.ts | 190 ++--- .../logic-function-resource.service.ts | 17 + .../__tests__/workspace.service.spec.ts | 2 + .../workspace/services/workspace.service.ts | 25 +- .../workspace/workspace.module.ts | 3 + .../data/services/dev-seeder-data.service.ts | 16 +- .../dev-seeder/dev-seeder.module.ts | 2 + .../prefill-workflows.ts | 302 ------- .../prefill-logic-function.service.ts | 58 ++ .../standard-objects-prefill-data.ts | 32 - .../standard-objects-prefill.module.ts | 12 + .../prefill-companies.util.ts} | 0 .../prefill-dashboards.util.ts} | 0 .../prefill-opportunities.util.ts} | 4 +- .../prefill-people.util.ts} | 2 +- ...workflow-code-step-logic-functions.util.ts | 336 ++++++++ ...efill-workflow-command-menu-items.util.ts} | 2 +- .../utils/prefill-workflows.util.ts | 762 ++++++++++++++++++ 20 files changed, 1350 insertions(+), 467 deletions(-) delete mode 100644 packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-workflows.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/services/prefill-logic-function.service.ts delete mode 100644 packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/standard-objects-prefill-data.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/standard-objects-prefill.module.ts rename packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/{prefill-companies.ts => utils/prefill-companies.util.ts} (100%) rename packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/{prefill-dashboards.ts => utils/prefill-dashboards.util.ts} (100%) rename packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/{prefill-opportunities.ts => utils/prefill-opportunities.util.ts} (98%) rename packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/{prefill-people.ts => utils/prefill-people.util.ts} (99%) create mode 100644 packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-workflow-code-step-logic-functions.util.ts rename packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/{prefill-workflow-command-menu-items.ts => utils/prefill-workflow-command-menu-items.util.ts} (96%) create mode 100644 packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-workflows.util.ts diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql index 51489facfc..0f4a85bde4 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql +++ b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql @@ -2273,6 +2273,11 @@ type FieldConnection { edges: [FieldEdge!]! } +type LogicFunctionLogs { + """Execution Logs""" + logs: String! +} + type DeleteTwoFactorAuthenticationMethod { """Boolean that confirms query was dispatched""" success: Boolean! @@ -2593,11 +2598,6 @@ type UsageAnalytics { userDailyUsage: UsageUserDaily } -type LogicFunctionLogs { - """Execution Logs""" - logs: String! -} - type FrontComponent { id: UUID! name: String! diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.ts b/packages/twenty-client-sdk/src/metadata/generated/schema.ts index 52fd7b1c52..0cfe42ab32 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/schema.ts +++ b/packages/twenty-client-sdk/src/metadata/generated/schema.ts @@ -1937,6 +1937,12 @@ export interface FieldConnection { __typename: 'FieldConnection' } +export interface LogicFunctionLogs { + /** Execution Logs */ + logs: Scalars['String'] + __typename: 'LogicFunctionLogs' +} + export interface DeleteTwoFactorAuthenticationMethod { /** Boolean that confirms query was dispatched */ success: Scalars['Boolean'] @@ -2298,12 +2304,6 @@ export interface UsageAnalytics { __typename: 'UsageAnalytics' } -export interface LogicFunctionLogs { - /** Execution Logs */ - logs: Scalars['String'] - __typename: 'LogicFunctionLogs' -} - export interface FrontComponent { id: Scalars['UUID'] name: Scalars['String'] @@ -5097,6 +5097,13 @@ export interface FieldConnectionGenqlSelection{ __scalar?: boolean | number } +export interface LogicFunctionLogsGenqlSelection{ + /** Execution Logs */ + logs?: boolean | number + __typename?: boolean | number + __scalar?: boolean | number +} + export interface DeleteTwoFactorAuthenticationMethodGenqlSelection{ /** Boolean that confirms query was dispatched */ success?: boolean | number @@ -5502,13 +5509,6 @@ export interface UsageAnalyticsGenqlSelection{ __scalar?: boolean | number } -export interface LogicFunctionLogsGenqlSelection{ - /** Execution Logs */ - logs?: boolean | number - __typename?: boolean | number - __scalar?: boolean | number -} - export interface FrontComponentGenqlSelection{ id?: boolean | number name?: boolean | number @@ -8049,6 +8049,14 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null + const LogicFunctionLogs_possibleTypes: string[] = ['LogicFunctionLogs'] + export const isLogicFunctionLogs = (obj?: { __typename?: any } | null): obj is LogicFunctionLogs => { + if (!obj?.__typename) throw new Error('__typename is missing in "isLogicFunctionLogs"') + return LogicFunctionLogs_possibleTypes.includes(obj.__typename) + } + + + const DeleteTwoFactorAuthenticationMethod_possibleTypes: string[] = ['DeleteTwoFactorAuthenticationMethod'] export const isDeleteTwoFactorAuthenticationMethod = (obj?: { __typename?: any } | null): obj is DeleteTwoFactorAuthenticationMethod => { if (!obj?.__typename) throw new Error('__typename is missing in "isDeleteTwoFactorAuthenticationMethod"') @@ -8433,14 +8441,6 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null - const LogicFunctionLogs_possibleTypes: string[] = ['LogicFunctionLogs'] - export const isLogicFunctionLogs = (obj?: { __typename?: any } | null): obj is LogicFunctionLogs => { - if (!obj?.__typename) throw new Error('__typename is missing in "isLogicFunctionLogs"') - return LogicFunctionLogs_possibleTypes.includes(obj.__typename) - } - - - const FrontComponent_possibleTypes: string[] = ['FrontComponent'] export const isFrontComponent = (obj?: { __typename?: any } | null): obj is FrontComponent => { if (!obj?.__typename) throw new Error('__typename is missing in "isFrontComponent"') diff --git a/packages/twenty-client-sdk/src/metadata/generated/types.ts b/packages/twenty-client-sdk/src/metadata/generated/types.ts index 3b47795bc8..af6d183c54 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/types.ts +++ b/packages/twenty-client-sdk/src/metadata/generated/types.ts @@ -63,8 +63,8 @@ export default { 210, 227, 244, - 282, 283, + 284, 299, 300, 325, @@ -4458,6 +4458,14 @@ export default { 1 ] }, + "LogicFunctionLogs": { + "logs": [ + 1 + ], + "__typename": [ + 1 + ] + }, "DeleteTwoFactorAuthenticationMethod": { "success": [ 6 @@ -4503,10 +4511,10 @@ export default { }, "AuthTokenPair": { "accessOrWorkspaceAgnosticToken": [ - 250 + 251 ], "refreshToken": [ - 250 + 251 ], "__typename": [ 1 @@ -4514,7 +4522,7 @@ export default { }, "AvailableWorkspacesAndAccessTokens": { "tokens": [ - 251 + 252 ], "availableWorkspaces": [ 224 @@ -4566,10 +4574,10 @@ export default { }, "SignUp": { "loginToken": [ - 250 + 251 ], "workspace": [ - 256 + 257 ], "__typename": [ 1 @@ -4577,7 +4585,7 @@ export default { }, "TransientToken": { "transientToken": [ - 250 + 251 ], "__typename": [ 1 @@ -4599,7 +4607,7 @@ export default { }, "VerifyEmailAndGetLoginToken": { "loginToken": [ - 250 + 251 ], "workspaceUrls": [ 157 @@ -4618,7 +4626,7 @@ export default { }, "AuthTokens": { "tokens": [ - 251 + 252 ], "__typename": [ 1 @@ -4626,7 +4634,7 @@ export default { }, "LoginToken": { "loginToken": [ - 250 + 251 ], "__typename": [ 1 @@ -4656,10 +4664,10 @@ export default { }, "Impersonate": { "loginToken": [ - 250 + 251 ], "workspace": [ - 256 + 257 ], "__typename": [ 1 @@ -4689,10 +4697,10 @@ export default { }, "ApplicationTokenPair": { "applicationAccessToken": [ - 250 + 251 ], "applicationRefreshToken": [ - 250 + 251 ], "__typename": [ 1 @@ -4764,7 +4772,7 @@ export default { 1 ], "fields": [ - 271 + 272 ], "__typename": [ 1 @@ -4861,10 +4869,10 @@ export default { 6 ], "objectPermissions": [ - 275 + 276 ], "fieldPermissions": [ - 276 + 277 ], "permissionFlags": [ 1 @@ -4914,19 +4922,19 @@ export default { 1 ], "objects": [ - 272 - ], - "fields": [ - 271 - ], - "logicFunctions": [ 273 ], - "frontComponents": [ + "fields": [ + 272 + ], + "logicFunctions": [ 274 ], + "frontComponents": [ + 275 + ], "defaultRole": [ - 277 + 278 ], "sourcePackage": [ 1 @@ -4986,13 +4994,13 @@ export default { 1 ], "driver": [ - 282 - ], - "status": [ 283 ], + "status": [ + 284 + ], "verificationRecords": [ - 280 + 281 ], "verifiedAt": [ 4 @@ -5039,7 +5047,7 @@ export default { 1 ], "location": [ - 285 + 286 ], "__typename": [ 1 @@ -5067,13 +5075,13 @@ export default { }, "ImapSmtpCaldavConnectionParameters": { "IMAP": [ - 287 + 288 ], "SMTP": [ - 287 + 288 ], "CALDAV": [ - 287 + 288 ], "__typename": [ 1 @@ -5093,7 +5101,7 @@ export default { 3 ], "connectionParameters": [ - 288 + 289 ], "__typename": [ 1 @@ -5157,7 +5165,7 @@ export default { 1 ], "dailyUsage": [ - 293 + 294 ], "__typename": [ 1 @@ -5165,13 +5173,13 @@ export default { }, "UsageAnalytics": { "usageByUser": [ - 292 + 293 ], "usageByOperationType": [ - 292 + 293 ], "timeSeries": [ - 293 + 294 ], "periodStart": [ 4 @@ -5180,15 +5188,7 @@ export default { 4 ], "userDailyUsage": [ - 294 - ], - "__typename": [ - 1 - ] - }, - "LogicFunctionLogs": { - "logs": [ - 1 + 295 ], "__typename": [ 1 @@ -5235,7 +5235,7 @@ export default { 6 ], "applicationTokenPair": [ - 269 + 270 ], "__typename": [ 1 @@ -6656,7 +6656,7 @@ export default { } ], "checkUserExists": [ - 264, + 265, { "email": [ 1, @@ -6668,7 +6668,7 @@ export default { } ], "checkWorkspaceInviteHashIsValid": [ - 265, + 266, { "inviteHash": [ 1, @@ -6686,7 +6686,7 @@ export default { } ], "validatePasswordResetToken": [ - 259, + 260, { "passwordResetToken": [ 1, @@ -6769,7 +6769,7 @@ export default { 220 ], "getConnectedImapSmtpCaldavAccount": [ - 289, + 290, { "id": [ 3, @@ -6778,7 +6778,7 @@ export default { } ], "getAutoCompleteAddress": [ - 284, + 285, { "address": [ 1, @@ -6797,7 +6797,7 @@ export default { } ], "getAddressDetails": [ - 286, + 287, { "placeId": [ 1, @@ -6889,19 +6889,19 @@ export default { } ], "getPostgresCredentials": [ - 291 + 292 ], "findManyPublicDomains": [ - 279 + 280 ], "getEmailingDomains": [ - 281 + 282 ], "findManyMarketplaceApps": [ - 278 + 279 ], "findOneMarketplaceApp": [ - 278, + 279, { "universalIdentifier": [ 1, @@ -6924,7 +6924,7 @@ export default { } ], "getUsageAnalytics": [ - 295, + 296, { "input": [ 365 @@ -8309,7 +8309,7 @@ export default { } ], "getAuthorizationUrlForSSO": [ - 254, + 255, { "input": [ 466, @@ -8318,7 +8318,7 @@ export default { } ], "getLoginTokenFromCredentials": [ - 263, + 264, { "email": [ 1, @@ -8344,7 +8344,7 @@ export default { } ], "signIn": [ - 252, + 253, { "email": [ 1, @@ -8366,7 +8366,7 @@ export default { } ], "verifyEmailAndGetLoginToken": [ - 260, + 261, { "emailVerificationToken": [ 1, @@ -8386,7 +8386,7 @@ export default { } ], "verifyEmailAndGetWorkspaceAgnosticToken": [ - 252, + 253, { "emailVerificationToken": [ 1, @@ -8402,7 +8402,7 @@ export default { } ], "getAuthTokensFromOTP": [ - 262, + 263, { "otp": [ 1, @@ -8422,7 +8422,7 @@ export default { } ], "signUp": [ - 252, + 253, { "email": [ 1, @@ -8444,7 +8444,7 @@ export default { } ], "signUpInWorkspace": [ - 257, + 258, { "email": [ 1, @@ -8475,13 +8475,13 @@ export default { } ], "signUpInNewWorkspace": [ - 257 - ], - "generateTransientToken": [ 258 ], + "generateTransientToken": [ + 259 + ], "getAuthTokensFromLoginToken": [ - 262, + 263, { "loginToken": [ 1, @@ -8494,7 +8494,7 @@ export default { } ], "authorizeApp": [ - 249, + 250, { "clientId": [ 1, @@ -8516,7 +8516,7 @@ export default { } ], "renewToken": [ - 262, + 263, { "appToken": [ 1, @@ -8525,7 +8525,7 @@ export default { } ], "generateApiKeyToken": [ - 261, + 262, { "apiKeyId": [ 3, @@ -8538,7 +8538,7 @@ export default { } ], "emailPasswordResetLink": [ - 253, + 254, { "email": [ 1, @@ -8550,7 +8550,7 @@ export default { } ], "updatePasswordViaResetToken": [ - 255, + 256, { "passwordResetToken": [ 1, @@ -8651,7 +8651,7 @@ export default { } ], "initiateOTPProvisioning": [ - 247, + 248, { "loginToken": [ 1, @@ -8664,10 +8664,10 @@ export default { } ], "initiateOTPProvisioningForAuthenticatedUser": [ - 247 + 248 ], "deleteTwoFactorAuthenticationMethod": [ - 246, + 247, { "twoFactorAuthenticationMethodId": [ 3, @@ -8676,7 +8676,7 @@ export default { } ], "verifyTwoFactorAuthenticationMethodForAuthenticatedUser": [ - 248, + 249, { "otp": [ 1, @@ -8782,7 +8782,7 @@ export default { } ], "impersonate": [ - 266, + 267, { "userId": [ 3, @@ -8804,7 +8804,7 @@ export default { } ], "saveImapSmtpCaldavAccount": [ - 290, + 291, { "accountOwnerId": [ 3, @@ -9007,13 +9007,13 @@ export default { } ], "enablePostgresProxy": [ - 291 + 292 ], "disablePostgresProxy": [ - 291 + 292 ], "createPublicDomain": [ - 279, + 280, { "domain": [ 1, @@ -9040,14 +9040,14 @@ export default { } ], "createEmailingDomain": [ - 281, + 282, { "domain": [ 1, "String!" ], "driver": [ - 282, + 283, "EmailingDomainDriver!" ] } @@ -9062,7 +9062,7 @@ export default { } ], "verifyEmailingDomain": [ - 281, + 282, { "id": [ 1, @@ -9139,7 +9139,7 @@ export default { } ], "createDevelopmentApplication": [ - 267, + 268, { "universalIdentifier": [ 1, @@ -9152,7 +9152,7 @@ export default { } ], "generateApplicationToken": [ - 269, + 270, { "applicationId": [ 3, @@ -9161,7 +9161,7 @@ export default { } ], "syncApplication": [ - 268, + 269, { "manifest": [ 15, @@ -9170,7 +9170,7 @@ export default { } ], "uploadApplicationFile": [ - 270, + 271, { "file": [ 372, @@ -9204,7 +9204,7 @@ export default { } ], "renewApplicationToken": [ - 269, + 270, { "applicationRefreshToken": [ 1, @@ -11464,7 +11464,7 @@ export default { } ], "logicFunctionLogs": [ - 296, + 246, { "input": [ 490, diff --git a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-resource/logic-function-resource.service.ts b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-resource/logic-function-resource.service.ts index f094a8f80c..ec8eb90fd7 100644 --- a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-resource/logic-function-resource.service.ts +++ b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-resource/logic-function-resource.service.ts @@ -3,6 +3,7 @@ import { Injectable } from '@nestjs/common'; import crypto from 'crypto'; import { promises as fs } from 'fs'; import { dirname, join } from 'path'; +import { type QueryRunner } from 'typeorm'; import { FileFolder } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; @@ -40,6 +41,7 @@ type UpdateSourceFilesParams = Omit< 'builtHandlerPath' > & { sourceHandlerCode: string; + queryRunner?: QueryRunner; }; type GetSourceCodeParams = Identifier & { @@ -128,6 +130,7 @@ export class LogicFunctionResourceService { workspaceId, applicationUniversalIdentifier, sourceHandlerCode, + queryRunner, }: UpdateSourceFilesParams): Promise { await this.fileStorageService.writeFile({ workspaceId, @@ -137,6 +140,20 @@ export class LogicFunctionResourceService { sourceFile: sourceHandlerCode, settings: { isTemporaryFile: false, toDelete: false }, mimeType: 'application/typescript', + queryRunner, + }); + } + + async deleteSourceFile({ + sourceHandlerPath, + workspaceId, + applicationUniversalIdentifier, + }: GetSourceCodeParams): Promise { + await this.fileStorageService.delete({ + workspaceId, + applicationUniversalIdentifier, + fileFolder: FileFolder.Source, + resourcePath: sourceHandlerPath, }); } diff --git a/packages/twenty-server/src/engine/core-modules/workspace/services/__tests__/workspace.service.spec.ts b/packages/twenty-server/src/engine/core-modules/workspace/services/__tests__/workspace.service.spec.ts index 69618c0fd8..4a52dbc6f3 100644 --- a/packages/twenty-server/src/engine/core-modules/workspace/services/__tests__/workspace.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/workspace/services/__tests__/workspace.service.spec.ts @@ -33,6 +33,7 @@ import { AiModelRegistryService } from 'src/engine/metadata-modules/ai/ai-models import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service'; import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service'; import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service'; +import { PrefillLogicFunctionService } from 'src/engine/workspace-manager/standard-objects-prefill-data/services/prefill-logic-function.service'; import { WorkspaceManagerService } from 'src/engine/workspace-manager/workspace-manager.service'; describe('WorkspaceService', () => { @@ -119,6 +120,7 @@ describe('WorkspaceService', () => { PermissionsService, FileCorePictureService, AiModelRegistryService, + PrefillLogicFunctionService, ].map((service) => ({ provide: service, useValue: {}, diff --git a/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts b/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts index 1f4093c966..5cd0b9b230 100644 --- a/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts +++ b/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts @@ -53,12 +53,14 @@ import { PermissionsService } from 'src/engine/metadata-modules/permissions/perm import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service'; import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util'; import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service'; -import { prefillCompanies } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-companies'; -import { prefillDashboards } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-dashboards'; -import { prefillOpportunities } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-opportunities'; -import { prefillPeople } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-people'; -import { prefillWorkflowCommandMenuItems } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-workflow-command-menu-items'; -import { prefillWorkflows } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-workflows'; +import { prefillCompanies } from 'src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-companies.util'; +import { prefillDashboards } from 'src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-dashboards.util'; +import { prefillOpportunities } from 'src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-opportunities.util'; +import { prefillPeople } from 'src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-people.util'; +import { prefillWorkflowCommandMenuItems } from 'src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-workflow-command-menu-items.util'; +import { getCreateCompanyWhenAddingNewPersonCodeStepLogicFunctionDefinitions } from 'src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-workflow-code-step-logic-functions.util'; +import { prefillWorkflows } from 'src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-workflows.util'; +import { PrefillLogicFunctionService } from 'src/engine/workspace-manager/standard-objects-prefill-data/services/prefill-logic-function.service'; import { WorkspaceManagerService } from 'src/engine/workspace-manager/workspace-manager.service'; import { DEFAULT_FEATURE_FLAGS } from 'src/engine/workspace-manager/workspace-migration/constant/default-feature-flags'; import { extractVersionMajorMinorPatch } from 'src/utils/version/extract-version-major-minor-patch'; @@ -111,6 +113,7 @@ export class WorkspaceService extends TypeOrmQueryService { private readonly permissionsService: PermissionsService, private readonly dnsManagerService: DnsManagerService, private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService, + private readonly prefillLogicFunctionService: PrefillLogicFunctionService, private readonly workspaceCacheStorageService: WorkspaceCacheStorageService, private readonly subdomainManagerService: SubdomainManagerService, private readonly workspaceDataSourceService: WorkspaceDataSourceService, @@ -681,6 +684,14 @@ export class WorkspaceService extends TypeOrmQueryService { }, ); + await this.prefillLogicFunctionService.ensureSeeded({ + workspaceId, + definitions: + getCreateCompanyWhenAddingNewPersonCodeStepLogicFunctionDefinitions( + workspaceId, + ), + }); + const queryRunner = this.coreDataSource.createQueryRunner(); await queryRunner.connect(); @@ -694,6 +705,7 @@ export class WorkspaceService extends TypeOrmQueryService { await prefillWorkflows( queryRunner.manager, + workspaceId, schemaName, flatObjectMetadataMaps, flatFieldMetadataMaps, @@ -720,6 +732,7 @@ export class WorkspaceService extends TypeOrmQueryService { ); } } + throw error; } finally { await queryRunner.release(); diff --git a/packages/twenty-server/src/engine/core-modules/workspace/workspace.module.ts b/packages/twenty-server/src/engine/core-modules/workspace/workspace.module.ts index 39a67d3a25..c9c9ae67e1 100644 --- a/packages/twenty-server/src/engine/core-modules/workspace/workspace.module.ts +++ b/packages/twenty-server/src/engine/core-modules/workspace/workspace.module.ts @@ -41,12 +41,14 @@ import { ViewModule } from 'src/engine/metadata-modules/view/view.module'; import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module'; import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module'; import { WorkspaceManagerModule } from 'src/engine/workspace-manager/workspace-manager.module'; +import { StandardObjectsPrefillModule } from 'src/engine/workspace-manager/standard-objects-prefill-data/standard-objects-prefill.module'; @Module({ imports: [ TypeORMModule, TypeOrmModule.forFeature([BillingSubscriptionEntity, WorkspaceEntity]), MetricsModule, + StandardObjectsPrefillModule, NestjsQueryGraphQLModule.forFeature({ imports: [ AuditModule, @@ -79,6 +81,7 @@ import { WorkspaceManagerModule } from 'src/engine/workspace-manager/workspace-m WorkspaceManyOrAllFlatEntityMapsCacheModule, ApplicationModule, EnterpriseModule, + StandardObjectsPrefillModule, ], services: [WorkspaceService], resolvers: workspaceAutoResolverOpts, diff --git a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/services/dev-seeder-data.service.ts b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/services/dev-seeder-data.service.ts index 87582b7a29..10a34164f3 100644 --- a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/services/dev-seeder-data.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/services/dev-seeder-data.service.ts @@ -121,8 +121,10 @@ import { WORKSPACE_MEMBER_DATA_SEED_COLUMNS, } from 'src/engine/workspace-manager/dev-seeder/data/constants/workspace-member-data-seeds.constant'; import { TimelineActivitySeederService } from 'src/engine/workspace-manager/dev-seeder/data/services/timeline-activity-seeder.service'; -import { prefillWorkflowCommandMenuItems } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-workflow-command-menu-items'; -import { prefillWorkflows } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-workflows'; +import { prefillWorkflowCommandMenuItems } from 'src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-workflow-command-menu-items.util'; +import { getCreateCompanyWhenAddingNewPersonCodeStepLogicFunctionDefinitions } from 'src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-workflow-code-step-logic-functions.util'; +import { prefillWorkflows } from 'src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-workflows.util'; +import { PrefillLogicFunctionService } from 'src/engine/workspace-manager/standard-objects-prefill-data/services/prefill-logic-function.service'; import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications'; type RecordSeedConfig = { @@ -304,6 +306,7 @@ export class DevSeederDataService { private readonly timelineActivitySeederService: TimelineActivitySeederService, private readonly fileStorageService: FileStorageService, private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService, + private readonly prefillLogicFunctionService: PrefillLogicFunctionService, ) {} public async seed({ @@ -331,6 +334,14 @@ export class DevSeederDataService { const { seeds: attachmentSeeds, fileSeedMetadata: attachmentFileMeta } = generateAttachmentSeedsForWorkspace(workspaceId); + await this.prefillLogicFunctionService.ensureSeeded({ + workspaceId, + definitions: + getCreateCompanyWhenAddingNewPersonCodeStepLogicFunctionDefinitions( + workspaceId, + ), + }); + await this.coreDataSource.transaction( async (entityManager: WorkspaceEntityManager) => { await this.seedRecordsInBatches({ @@ -359,6 +370,7 @@ export class DevSeederDataService { await prefillWorkflows( entityManager, + workspaceId, schemaName, flatObjectMetadataMaps, flatFieldMetadataMaps, diff --git a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/dev-seeder.module.ts b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/dev-seeder.module.ts index e1001bb1b3..0745151488 100644 --- a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/dev-seeder.module.ts +++ b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/dev-seeder.module.ts @@ -26,6 +26,7 @@ import { DevSeederDataService } from 'src/engine/workspace-manager/dev-seeder/da import { TimelineActivitySeederService } from 'src/engine/workspace-manager/dev-seeder/data/services/timeline-activity-seeder.service'; import { DevSeederMetadataService } from 'src/engine/workspace-manager/dev-seeder/metadata/services/dev-seeder-metadata.service'; import { DevSeederService } from 'src/engine/workspace-manager/dev-seeder/services/dev-seeder.service'; +import { StandardObjectsPrefillModule } from 'src/engine/workspace-manager/standard-objects-prefill-data/standard-objects-prefill.module'; import { TwentyStandardApplicationModule } from 'src/engine/workspace-manager/twenty-standard-application/twenty-standard-application.module'; import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration.module'; @@ -48,6 +49,7 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace TypeOrmModule.forFeature([WorkspaceEntity, ObjectMetadataEntity]), ObjectPermissionModule, WorkspaceManyOrAllFlatEntityMapsCacheModule, + StandardObjectsPrefillModule, WorkspaceCacheModule, WorkspaceMigrationModule, TwentyStandardApplicationModule, diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-workflows.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-workflows.ts deleted file mode 100644 index aed38abea4..0000000000 --- a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-workflows.ts +++ /dev/null @@ -1,302 +0,0 @@ -import { FieldActorSource } from 'twenty-shared/types'; -import { isDefined } from 'twenty-shared/utils'; -import { type EntityManager } from 'typeorm'; - -import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; -import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util'; -import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; -import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; -import { buildObjectIdByNameMaps } from 'src/engine/metadata-modules/flat-object-metadata/utils/build-object-id-by-name-maps.util'; -import { generateObjectRecordFields } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-object-record-fields'; - -export const QUICK_LEAD_WORKFLOW_ID = '8b213cac-a68b-4ffe-817a-3ec994e9932d'; -export const QUICK_LEAD_WORKFLOW_VERSION_ID = - 'ac67974f-c524-4288-9d88-af8515400b68'; - -export const prefillWorkflows = async ( - entityManager: EntityManager, - schemaName: string, - flatObjectMetadataMaps: FlatEntityMaps, - flatFieldMetadataMaps: FlatEntityMaps, -) => { - const { idByNameSingular: objectIdByNameSingular } = buildObjectIdByNameMaps( - flatObjectMetadataMaps, - ); - - const companyObjectMetadataId = objectIdByNameSingular['company']; - const personObjectMetadataId = objectIdByNameSingular['person']; - - if ( - !isDefined(companyObjectMetadataId) || - !isDefined(personObjectMetadataId) - ) { - throw new Error('Company or person object metadata not found'); - } - - const companyObjectMetadata = findFlatEntityByIdInFlatEntityMaps({ - flatEntityId: companyObjectMetadataId, - flatEntityMaps: flatObjectMetadataMaps, - }); - - const personObjectMetadata = findFlatEntityByIdInFlatEntityMaps({ - flatEntityId: personObjectMetadataId, - flatEntityMaps: flatObjectMetadataMaps, - }); - - if (!isDefined(companyObjectMetadata) || !isDefined(personObjectMetadata)) { - throw new Error('Company or person object metadata not found'); - } - - await entityManager - .createQueryBuilder() - .insert() - .into(`${schemaName}.workflow`, [ - 'id', - 'name', - 'lastPublishedVersionId', - 'statuses', - 'position', - 'createdBySource', - 'createdByWorkspaceMemberId', - 'createdByName', - 'createdByContext', - 'updatedBySource', - 'updatedByWorkspaceMemberId', - 'updatedByName', - ]) - .orIgnore() - .values([ - { - id: QUICK_LEAD_WORKFLOW_ID, - name: 'Quick Lead', - lastPublishedVersionId: QUICK_LEAD_WORKFLOW_VERSION_ID, - statuses: ['ACTIVE'], - position: 1, - createdBySource: FieldActorSource.SYSTEM, - createdByWorkspaceMemberId: null, - createdByName: 'System', - createdByContext: {}, - updatedBySource: FieldActorSource.SYSTEM, - updatedByWorkspaceMemberId: null, - updatedByName: 'System', - }, - ]) - .returning('*') - .execute(); - - await entityManager - .createQueryBuilder() - .insert() - .into(`${schemaName}.workflowVersion`, [ - 'id', - 'name', - 'trigger', - 'steps', - 'status', - 'position', - 'workflowId', - ]) - .orIgnore() - .values([ - { - id: QUICK_LEAD_WORKFLOW_VERSION_ID, - name: 'v1', - trigger: JSON.stringify({ - name: 'Launch manually', - type: 'MANUAL', - settings: { - outputSchema: {}, - icon: 'IconUserPlus', - availability: { type: 'GLOBAL', locations: undefined }, - }, - nextStepIds: ['6e089bc9-aabd-435f-865f-f31c01c8f4a7'], - }), - steps: JSON.stringify([ - { - id: '6e089bc9-aabd-435f-865f-f31c01c8f4a7', - name: 'Quick Lead Form', - type: 'FORM', - valid: false, - settings: { - input: [ - { - id: '14d669f0-5249-4fa4-b0bb-f8bd408328d5', - name: 'firstName', - type: 'TEXT', - label: 'First name', - placeholder: 'Tim', - }, - { - id: '4eb6ce85-d231-4aef-9837-744490c026d0', - name: 'lastName', - type: 'TEXT', - label: 'Last Name', - placeholder: 'Apple', - }, - { - id: 'adbf0e9f-1427-49be-b4fb-092b34d97350', - name: 'email', - type: 'TEXT', - label: 'Email', - placeholder: 'timapple@apple.com', - }, - { - id: '4ffc7992-9e65-4a4d-9baf-b52e62f2c273', - name: 'jobTitle', - type: 'TEXT', - label: 'Job title', - placeholder: 'CEO', - }, - { - id: '42f11926-04ea-4924-94a4-2293cc748362', - name: 'companyName', - type: 'TEXT', - label: 'Company name', - placeholder: 'Apple', - }, - { - id: 'd6ca80ee-26cd-466d-91bf-984d7205451c', - name: 'companyDomain', - type: 'TEXT', - label: 'Company domain', - placeholder: 'https://www.apple.com', - }, - ], - outputSchema: { - email: { - type: 'TEXT', - label: 'Email', - value: 'My text', - isLeaf: true, - }, - jobTitle: { - type: 'TEXT', - label: 'Job title', - value: 'My text', - isLeaf: true, - }, - lastName: { - type: 'TEXT', - label: 'Last Name', - value: 'My text', - isLeaf: true, - }, - firstName: { - type: 'TEXT', - label: 'First name', - value: 'My text', - isLeaf: true, - }, - companyName: { - type: 'TEXT', - label: 'Company name', - value: 'My text', - isLeaf: true, - }, - companyDomain: { - type: 'TEXT', - label: 'Company domain', - value: 'My text', - isLeaf: true, - }, - }, - errorHandlingOptions: { - retryOnFailure: { value: false }, - continueOnFailure: { value: false }, - }, - }, - __typename: 'WorkflowAction', - nextStepIds: ['0715b6cd-7cc1-4b98-971b-00f54dfe643b'], - }, - { - id: '0715b6cd-7cc1-4b98-971b-00f54dfe643b', - name: 'Create Company', - type: 'CREATE_RECORD', - valid: false, - settings: { - input: { - objectName: 'company', - objectRecord: { - name: '{{6e089bc9-aabd-435f-865f-f31c01c8f4a7.companyName}}', - domainName: { - primaryLinkUrl: - '{{6e089bc9-aabd-435f-865f-f31c01c8f4a7.companyDomain}}', - primaryLinkLabel: '', - }, - }, - }, - outputSchema: { - object: { - icon: 'IconBuildingSkyscraper', - label: 'Company', - value: 'A company', - isLeaf: true, - fieldIdName: 'id', - nameSingular: 'company', - }, - _outputSchemaType: 'RECORD', - fields: generateObjectRecordFields({ - objectMetadataInfo: { - flatObjectMetadata: companyObjectMetadata, - flatObjectMetadataMaps, - flatFieldMetadataMaps, - }, - }), - }, - errorHandlingOptions: { - retryOnFailure: { value: false }, - continueOnFailure: { value: false }, - }, - }, - __typename: 'WorkflowAction', - nextStepIds: ['6f553ea7-b00e-4371-9d88-d8298568a246'], - }, - { - id: '6f553ea7-b00e-4371-9d88-d8298568a246', - name: 'Create Person', - type: 'CREATE_RECORD', - valid: false, - settings: { - input: { - objectName: 'person', - objectRecord: { - name: { - lastName: - '{{6e089bc9-aabd-435f-865f-f31c01c8f4a7.lastName}}', - firstName: - '{{6e089bc9-aabd-435f-865f-f31c01c8f4a7.firstName}}', - }, - emails: { - primaryEmail: - '{{6e089bc9-aabd-435f-865f-f31c01c8f4a7.email}}', - additionalEmails: [], - }, - companyId: '{{0715b6cd-7cc1-4b98-971b-00f54dfe643b.id}}', - }, - }, - outputSchema: { - fields: generateObjectRecordFields({ - objectMetadataInfo: { - flatObjectMetadata: personObjectMetadata, - flatObjectMetadataMaps, - flatFieldMetadataMaps, - }, - }), - }, - errorHandlingOptions: { - retryOnFailure: { value: false }, - continueOnFailure: { value: false }, - }, - }, - __typename: 'WorkflowAction', - nextStepIds: null, - }, - ]), - status: 'ACTIVE', - position: 1, - workflowId: QUICK_LEAD_WORKFLOW_ID, - }, - ]) - .returning('*') - .execute(); -}; diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/services/prefill-logic-function.service.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/services/prefill-logic-function.service.ts new file mode 100644 index 0000000000..5175ba06cc --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/services/prefill-logic-function.service.ts @@ -0,0 +1,58 @@ +import { Injectable } from '@nestjs/common'; + +import { isDefined } from 'twenty-shared/utils'; + +import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service'; +import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util'; +import { LogicFunctionFromSourceService } from 'src/engine/metadata-modules/logic-function/services/logic-function-from-source.service'; +import { type PrefilledWorkflowCodeStepLogicFunctionDefinition } from 'src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-workflow-code-step-logic-functions.util'; + +@Injectable() +export class PrefillLogicFunctionService { + constructor( + private readonly logicFunctionFromSourceService: LogicFunctionFromSourceService, + private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService, + ) {} + + async ensureSeeded({ + workspaceId, + definitions, + }: { + workspaceId: string; + definitions: PrefilledWorkflowCodeStepLogicFunctionDefinition[]; + }) { + const { flatLogicFunctionMaps } = + await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatMapsKeys: ['flatLogicFunctionMaps'], + }, + ); + + for (const definition of definitions) { + const existingLogicFunction = findFlatEntityByIdInFlatEntityMaps({ + flatEntityId: definition.id, + flatEntityMaps: flatLogicFunctionMaps, + }); + + if (isDefined(existingLogicFunction)) { + continue; + } + + await this.logicFunctionFromSourceService.createOneFromSource({ + workspaceId, + input: { + id: definition.id, + name: definition.name, + description: definition.description, + toolInputSchema: definition.toolInputSchema, + source: { + sourceHandlerCode: definition.sourceHandlerCode, + toolInputSchema: definition.toolInputSchema, + handlerName: 'main', + }, + }, + }); + } + } +} diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/standard-objects-prefill-data.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/standard-objects-prefill-data.ts deleted file mode 100644 index ca2877d79b..0000000000 --- a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/standard-objects-prefill-data.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { type DataSource, type EntityManager } from 'typeorm'; - -import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; -import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; -import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; -import { prefillCompanies } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-companies'; -import { prefillPeople } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-people'; -import { prefillWorkflowCommandMenuItems } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-workflow-command-menu-items'; -import { prefillWorkflows } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-workflows'; - -export const standardObjectsPrefillData = async ( - dataSource: DataSource, - schemaName: string, - workspaceId: string, - flatObjectMetadataMaps: FlatEntityMaps, - flatFieldMetadataMaps: FlatEntityMaps, -) => { - dataSource.transaction(async (entityManager: EntityManager) => { - await prefillCompanies(entityManager, schemaName); - - await prefillPeople(entityManager, schemaName); - - await prefillWorkflows( - entityManager, - schemaName, - flatObjectMetadataMaps, - flatFieldMetadataMaps, - ); - - await prefillWorkflowCommandMenuItems(entityManager, workspaceId); - }); -}; diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/standard-objects-prefill.module.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/standard-objects-prefill.module.ts new file mode 100644 index 0000000000..d17277c31c --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/standard-objects-prefill.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; + +import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module'; +import { LogicFunctionModule } from 'src/engine/metadata-modules/logic-function/logic-function.module'; +import { PrefillLogicFunctionService } from 'src/engine/workspace-manager/standard-objects-prefill-data/services/prefill-logic-function.service'; + +@Module({ + imports: [LogicFunctionModule, WorkspaceManyOrAllFlatEntityMapsCacheModule], + providers: [PrefillLogicFunctionService], + exports: [PrefillLogicFunctionService], +}) +export class StandardObjectsPrefillModule {} diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-companies.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-companies.util.ts similarity index 100% rename from packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-companies.ts rename to packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-companies.util.ts diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-dashboards.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-dashboards.util.ts similarity index 100% rename from packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-dashboards.ts rename to packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-dashboards.util.ts diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-opportunities.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-opportunities.util.ts similarity index 98% rename from packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-opportunities.ts rename to packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-opportunities.util.ts index b496f10d6c..25beaf4957 100644 --- a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-opportunities.ts +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-opportunities.util.ts @@ -7,14 +7,14 @@ import { FIGMA_ID, NOTION_ID, STRIPE_ID, -} from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-companies'; +} from 'src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-companies.util'; import { BRIAN_CHESKY_ID, DARIO_AMODEI_ID, DYLAN_FIELD_ID, IVAN_ZHAO_ID, PATRICK_COLLISON_ID, -} from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-people'; +} from 'src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-people.util'; export const OPPORTUNITY_STRIPE_PLATFORM_MIGRATION_ID = '822639e5-9bf7-40f1-8882-a11140362339'; diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-people.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-people.util.ts similarity index 99% rename from packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-people.ts rename to packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-people.util.ts index 005d14da95..7f746452d6 100644 --- a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-people.ts +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-people.util.ts @@ -7,7 +7,7 @@ import { FIGMA_ID, NOTION_ID, STRIPE_ID, -} from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-companies'; +} from 'src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-companies.util'; export const BRIAN_CHESKY_ID = 'a2e78a5e-338b-46df-8811-fa08c7d19d35'; // Airbnb export const DARIO_AMODEI_ID = '93c72d2e-e65c-44c4-99ad-f87f50349dcf'; // Anthropic diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-workflow-code-step-logic-functions.util.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-workflow-code-step-logic-functions.util.ts new file mode 100644 index 0000000000..c5ff73b047 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-workflow-code-step-logic-functions.util.ts @@ -0,0 +1,336 @@ +import { v5 as uuidv5 } from 'uuid'; + +const CREATE_COMPANY_WHEN_ADDING_NEW_PERSON_LOGIC_FUNCTION_ID_NAMESPACE = + 'd41b0a2d-aa97-44dc-ad5d-89774164af27'; + +export const getCreateCompanyWhenAddingNewPersonCodeStepLogicFunctionIds = ( + workspaceId: string, +) => ({ + extractDomainLogicFunctionId: uuidv5( + `${workspaceId}:create-company-when-adding-new-person:extract-domain`, + CREATE_COMPANY_WHEN_ADDING_NEW_PERSON_LOGIC_FUNCTION_ID_NAMESPACE, + ), + findMatchingCompanyByDomainLogicFunctionId: uuidv5( + `${workspaceId}:create-company-when-adding-new-person:find-matching-company-by-domain`, + CREATE_COMPANY_WHEN_ADDING_NEW_PERSON_LOGIC_FUNCTION_ID_NAMESPACE, + ), + isPersonalEmailLogicFunctionId: uuidv5( + `${workspaceId}:create-company-when-adding-new-person:is-personal-email`, + CREATE_COMPANY_WHEN_ADDING_NEW_PERSON_LOGIC_FUNCTION_ID_NAMESPACE, + ), +}); + +const EXTRACT_DOMAIN_TOOL_INPUT_SCHEMA = { + type: 'object', + properties: { + email: { + type: 'string', + }, + }, + required: ['email'], +}; + +const IS_PERSONAL_EMAIL_TOOL_INPUT_SCHEMA = { + type: 'object', + properties: { + primaryEmail: { + type: 'string', + }, + }, + required: ['primaryEmail'], +}; + +const FIND_MATCHING_COMPANY_BY_DOMAIN_TOOL_INPUT_SCHEMA = { + type: 'object', + properties: { + companies: { + type: 'array', + items: { + type: 'object', + }, + }, + domain: { + type: 'string', + }, + }, + required: ['companies', 'domain'], +}; + +const EXTRACT_DOMAIN_LOGIC_FUNCTION_SOURCE = `const MULTI_PART_SUFFIXES = new Set([ + 'ac.uk', + 'co.in', + 'co.jp', + 'co.kr', + 'co.nz', + 'co.uk', + 'co.za', + 'com.ar', + 'com.au', + 'com.br', + 'com.cn', + 'com.hk', + 'com.mx', + 'com.sg', + 'com.tr', + 'com.tw', + 'com.ua', + 'gov.uk', + 'ne.jp', + 'net.au', + 'org.au', + 'org.uk', +]); + +const getRegistrableDomain = (host) => { + const normalizedHost = host.toLowerCase().replace(/^www\\./, ''); + const parts = normalizedHost.split('.').filter(Boolean); + + if (parts.length <= 2) { + return normalizedHost; + } + + const lastTwo = parts.slice(-2).join('.'); + + if (MULTI_PART_SUFFIXES.has(lastTwo) && parts.length >= 3) { + return parts.slice(-3).join('.'); + } + + return lastTwo; +}; + +export const main = async (params) => { + const email = + typeof params?.email === 'string' ? params.email.trim().toLowerCase() : ''; + const domainFromEmail = email.split('@')[1] ?? ''; + const domain = domainFromEmail ? getRegistrableDomain(domainFromEmail) : ''; + + return { + url: domain ? \`https://\${domain}\` : '', + domain, + }; +};`; + +const IS_PERSONAL_EMAIL_LOGIC_FUNCTION_SOURCE = `const MULTI_PART_SUFFIXES = new Set([ + 'ac.uk', + 'co.in', + 'co.jp', + 'co.kr', + 'co.nz', + 'co.uk', + 'co.za', + 'com.ar', + 'com.au', + 'com.br', + 'com.cn', + 'com.hk', + 'com.mx', + 'com.sg', + 'com.tr', + 'com.tw', + 'com.ua', + 'gov.uk', + 'ne.jp', + 'net.au', + 'org.au', + 'org.uk', +]); + +const PERSONAL_EMAIL_DOMAINS = new Set([ + 'aol.com', + 'att.net', + 'btinternet.com', + 'comcast.net', + 'fastmail.com', + 'free.fr', + 'gmx.com', + 'gmx.de', + 'googlemail.com', + 'hotmail.com', + 'hotmail.fr', + 'icloud.com', + 'laposte.net', + 'live.com', + 'mac.com', + 'mail.com', + 'me.com', + 'msn.com', + 'orange.fr', + 'outlook.com', + 'outlook.fr', + 'pm.me', + 'proton.me', + 'protonmail.com', + 'sfr.fr', + 'verizon.net', + 'wanadoo.fr', + 'yahoo.co.jp', + 'yahoo.co.uk', + 'yahoo.com', + 'yandex.com', + 'yandex.ru', + 'zoho.com', + 'gmail.com', +]); + +const getRegistrableDomain = (host) => { + const normalizedHost = host.toLowerCase().replace(/^www\\./, ''); + const parts = normalizedHost.split('.').filter(Boolean); + + if (parts.length <= 2) { + return normalizedHost; + } + + const lastTwo = parts.slice(-2).join('.'); + + if (MULTI_PART_SUFFIXES.has(lastTwo) && parts.length >= 3) { + return parts.slice(-3).join('.'); + } + + return lastTwo; +}; + +export const main = async (params) => { + const email = + typeof params?.primaryEmail === 'string' + ? params.primaryEmail.trim().toLowerCase() + : ''; + + if (!email || !email.includes('@')) { + return { isPersonal: true }; + } + + const host = email.split('@')[1] ?? ''; + const registrableDomain = host ? getRegistrableDomain(host) : ''; + + return { + isPersonal: + !registrableDomain || + PERSONAL_EMAIL_DOMAINS.has(host) || + PERSONAL_EMAIL_DOMAINS.has(registrableDomain), + }; +};`; + +const FIND_MATCHING_COMPANY_BY_DOMAIN_LOGIC_FUNCTION_SOURCE = `const MULTI_PART_SUFFIXES = new Set([ + 'ac.uk', + 'co.in', + 'co.jp', + 'co.kr', + 'co.nz', + 'co.uk', + 'co.za', + 'com.ar', + 'com.au', + 'com.br', + 'com.cn', + 'com.hk', + 'com.mx', + 'com.sg', + 'com.tr', + 'com.tw', + 'com.ua', + 'gov.uk', + 'ne.jp', + 'net.au', + 'org.au', + 'org.uk', +]); + +const normalizeHost = (value) => { + if (typeof value !== 'string') { + return ''; + } + + return value + .trim() + .toLowerCase() + .replace(/^https?:\\/\\//, '') + .replace(/^www\\./, '') + .split('/')[0] + .split('?')[0] + .split('#')[0] + .split(':')[0]; +}; + +const getRegistrableDomain = (value) => { + const normalizedHost = normalizeHost(value); + const parts = normalizedHost.split('.').filter(Boolean); + + if (parts.length <= 2) { + return normalizedHost; + } + + const lastTwo = parts.slice(-2).join('.'); + + if (MULTI_PART_SUFFIXES.has(lastTwo) && parts.length >= 3) { + return parts.slice(-3).join('.'); + } + + return lastTwo; +}; + +export const main = async (params) => { + const domain = getRegistrableDomain(params?.domain); + const companies = Array.isArray(params?.companies) ? params.companies : []; + + const matchingCompany = companies.find((company) => { + const companyDomain = getRegistrableDomain( + company?.domainName?.primaryLinkUrl, + ); + + return companyDomain !== '' && companyDomain === domain; + }); + + const companyId = + typeof matchingCompany?.id === 'string' ? matchingCompany.id : ''; + + return { + companyId, + hasMatch: companyId !== '', + }; +};`; + +export type PrefilledWorkflowCodeStepLogicFunctionDefinition = { + id: string; + name: string; + description: string; + sourceHandlerCode: string; + toolInputSchema: object; +}; + +export const getCreateCompanyWhenAddingNewPersonCodeStepLogicFunctionDefinitions = + (workspaceId: string): PrefilledWorkflowCodeStepLogicFunctionDefinition[] => { + const { + extractDomainLogicFunctionId, + findMatchingCompanyByDomainLogicFunctionId, + isPersonalEmailLogicFunctionId, + } = + getCreateCompanyWhenAddingNewPersonCodeStepLogicFunctionIds(workspaceId); + + return [ + { + id: extractDomainLogicFunctionId, + name: 'Extract domain from email', + description: + 'Extracts a normalized company domain and URL from a person email address.', + sourceHandlerCode: EXTRACT_DOMAIN_LOGIC_FUNCTION_SOURCE, + toolInputSchema: EXTRACT_DOMAIN_TOOL_INPUT_SCHEMA, + }, + { + id: findMatchingCompanyByDomainLogicFunctionId, + name: 'Find matching company by domain', + description: + 'Finds an existing company whose website matches a normalized registrable domain.', + sourceHandlerCode: + FIND_MATCHING_COMPANY_BY_DOMAIN_LOGIC_FUNCTION_SOURCE, + toolInputSchema: FIND_MATCHING_COMPANY_BY_DOMAIN_TOOL_INPUT_SCHEMA, + }, + { + id: isPersonalEmailLogicFunctionId, + name: 'Is this a personal email?', + description: + 'Detects whether an email address belongs to a common personal email provider.', + sourceHandlerCode: IS_PERSONAL_EMAIL_LOGIC_FUNCTION_SOURCE, + toolInputSchema: IS_PERSONAL_EMAIL_TOOL_INPUT_SCHEMA, + }, + ]; + }; diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-workflow-command-menu-items.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-workflow-command-menu-items.util.ts similarity index 96% rename from packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-workflow-command-menu-items.ts rename to packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-workflow-command-menu-items.util.ts index 1ccfd9d16f..19b95706bc 100644 --- a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-workflow-command-menu-items.ts +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-workflow-command-menu-items.util.ts @@ -2,7 +2,7 @@ import { type EntityManager } from 'typeorm'; import { CommandMenuItemAvailabilityType } from 'src/engine/metadata-modules/command-menu-item/enums/command-menu-item-availability-type.enum'; import { EngineComponentKey } from 'src/engine/metadata-modules/command-menu-item/enums/engine-component-key.enum'; -import { QUICK_LEAD_WORKFLOW_VERSION_ID } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-workflows'; +import { QUICK_LEAD_WORKFLOW_VERSION_ID } from 'src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-workflows.util'; import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications'; const QUICK_LEAD_COMMAND_MENU_ITEM_UNIVERSAL_IDENTIFIER = diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-workflows.util.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-workflows.util.ts new file mode 100644 index 0000000000..5eb4f49e5e --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-workflows.util.ts @@ -0,0 +1,762 @@ +import { FieldActorSource } from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; +import { type EntityManager } from 'typeorm'; + +import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; +import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; +import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util'; +import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; +import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; +import { buildObjectIdByNameMaps } from 'src/engine/metadata-modules/flat-object-metadata/utils/build-object-id-by-name-maps.util'; +import { generateFakeObjectRecordEvent } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-object-record-event'; +import { generateObjectRecordFields } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-object-record-fields'; +import { getCreateCompanyWhenAddingNewPersonCodeStepLogicFunctionIds } from 'src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-workflow-code-step-logic-functions.util'; + +export const QUICK_LEAD_WORKFLOW_ID = '8b213cac-a68b-4ffe-817a-3ec994e9932d'; +export const QUICK_LEAD_WORKFLOW_VERSION_ID = + 'ac67974f-c524-4288-9d88-af8515400b68'; +export const CREATE_COMPANY_WHEN_ADDING_NEW_PERSON_WORKFLOW_ID = + '887c6c06-fbc5-4b45-8d6b-f7b6b0f40b12'; +export const CREATE_COMPANY_WHEN_ADDING_NEW_PERSON_WORKFLOW_VERSION_ID = + '0f276d7e-a950-41ab-ad98-35e80753dc58'; +export const CREATE_COMPANY_WHEN_ADDING_NEW_PERSON_AUTOMATED_TRIGGER_ID = + 'c54f5990-13a3-4c3b-b75d-df09e7843036'; + +export const prefillWorkflows = async ( + entityManager: EntityManager, + workspaceId: string, + schemaName: string, + flatObjectMetadataMaps: FlatEntityMaps, + flatFieldMetadataMaps: FlatEntityMaps, +) => { + const { + extractDomainLogicFunctionId, + findMatchingCompanyByDomainLogicFunctionId, + isPersonalEmailLogicFunctionId, + } = getCreateCompanyWhenAddingNewPersonCodeStepLogicFunctionIds(workspaceId); + + const { idByNameSingular: objectIdByNameSingular } = buildObjectIdByNameMaps( + flatObjectMetadataMaps, + ); + + const companyObjectMetadataId = objectIdByNameSingular['company']; + const personObjectMetadataId = objectIdByNameSingular['person']; + + if ( + !isDefined(companyObjectMetadataId) || + !isDefined(personObjectMetadataId) + ) { + throw new Error('Company or person object metadata not found'); + } + + const companyObjectMetadata = findFlatEntityByIdInFlatEntityMaps({ + flatEntityId: companyObjectMetadataId, + flatEntityMaps: flatObjectMetadataMaps, + }); + + const personObjectMetadata = findFlatEntityByIdInFlatEntityMaps({ + flatEntityId: personObjectMetadataId, + flatEntityMaps: flatObjectMetadataMaps, + }); + + if (!isDefined(companyObjectMetadata) || !isDefined(personObjectMetadata)) { + throw new Error('Company or person object metadata not found'); + } + + const companyDomainNameFieldMetadata = Object.values( + flatFieldMetadataMaps.byUniversalIdentifier, + ).find( + (fieldMetadata) => + fieldMetadata?.objectMetadataId === companyObjectMetadataId && + fieldMetadata?.name === 'domainName', + ); + + if (!isDefined(companyDomainNameFieldMetadata)) { + throw new Error('Company domainName field metadata not found'); + } + + await entityManager + .createQueryBuilder() + .insert() + .into(`${schemaName}.workflow`, [ + 'id', + 'name', + 'lastPublishedVersionId', + 'statuses', + 'position', + 'createdBySource', + 'createdByWorkspaceMemberId', + 'createdByName', + 'createdByContext', + 'updatedBySource', + 'updatedByWorkspaceMemberId', + 'updatedByName', + ]) + .orIgnore() + .values([ + { + id: QUICK_LEAD_WORKFLOW_ID, + name: 'Quick Lead', + lastPublishedVersionId: QUICK_LEAD_WORKFLOW_VERSION_ID, + statuses: ['ACTIVE'], + position: 1, + createdBySource: FieldActorSource.SYSTEM, + createdByWorkspaceMemberId: null, + createdByName: 'System', + createdByContext: {}, + updatedBySource: FieldActorSource.SYSTEM, + updatedByWorkspaceMemberId: null, + updatedByName: 'System', + }, + { + id: CREATE_COMPANY_WHEN_ADDING_NEW_PERSON_WORKFLOW_ID, + name: 'Create company when adding a new person', + lastPublishedVersionId: + CREATE_COMPANY_WHEN_ADDING_NEW_PERSON_WORKFLOW_VERSION_ID, + statuses: ['ACTIVE'], + position: 2, + createdBySource: FieldActorSource.SYSTEM, + createdByWorkspaceMemberId: null, + createdByName: 'System', + createdByContext: {}, + updatedBySource: FieldActorSource.SYSTEM, + updatedByWorkspaceMemberId: null, + updatedByName: 'System', + }, + ]) + .returning('*') + .execute(); + + await entityManager + .createQueryBuilder() + .insert() + .into(`${schemaName}.workflowVersion`, [ + 'id', + 'name', + 'trigger', + 'steps', + 'status', + 'position', + 'workflowId', + ]) + .orIgnore() + .values([ + { + id: QUICK_LEAD_WORKFLOW_VERSION_ID, + name: 'v1', + trigger: JSON.stringify({ + name: 'Launch manually', + type: 'MANUAL', + settings: { + outputSchema: {}, + icon: 'IconUserPlus', + availability: { type: 'GLOBAL', locations: undefined }, + }, + nextStepIds: ['6e089bc9-aabd-435f-865f-f31c01c8f4a7'], + }), + steps: JSON.stringify([ + { + id: '6e089bc9-aabd-435f-865f-f31c01c8f4a7', + name: 'Quick Lead Form', + type: 'FORM', + valid: false, + settings: { + input: [ + { + id: '14d669f0-5249-4fa4-b0bb-f8bd408328d5', + name: 'firstName', + type: 'TEXT', + label: 'First name', + placeholder: 'Tim', + }, + { + id: '4eb6ce85-d231-4aef-9837-744490c026d0', + name: 'lastName', + type: 'TEXT', + label: 'Last Name', + placeholder: 'Apple', + }, + { + id: 'adbf0e9f-1427-49be-b4fb-092b34d97350', + name: 'email', + type: 'TEXT', + label: 'Email', + placeholder: 'timapple@apple.com', + }, + { + id: '4ffc7992-9e65-4a4d-9baf-b52e62f2c273', + name: 'jobTitle', + type: 'TEXT', + label: 'Job title', + placeholder: 'CEO', + }, + { + id: '42f11926-04ea-4924-94a4-2293cc748362', + name: 'companyName', + type: 'TEXT', + label: 'Company name', + placeholder: 'Apple', + }, + { + id: 'd6ca80ee-26cd-466d-91bf-984d7205451c', + name: 'companyDomain', + type: 'TEXT', + label: 'Company domain', + placeholder: 'https://www.apple.com', + }, + ], + outputSchema: { + email: { + type: 'TEXT', + label: 'Email', + value: 'My text', + isLeaf: true, + }, + jobTitle: { + type: 'TEXT', + label: 'Job title', + value: 'My text', + isLeaf: true, + }, + lastName: { + type: 'TEXT', + label: 'Last Name', + value: 'My text', + isLeaf: true, + }, + firstName: { + type: 'TEXT', + label: 'First name', + value: 'My text', + isLeaf: true, + }, + companyName: { + type: 'TEXT', + label: 'Company name', + value: 'My text', + isLeaf: true, + }, + companyDomain: { + type: 'TEXT', + label: 'Company domain', + value: 'My text', + isLeaf: true, + }, + }, + errorHandlingOptions: { + retryOnFailure: { value: false }, + continueOnFailure: { value: false }, + }, + }, + __typename: 'WorkflowAction', + nextStepIds: ['0715b6cd-7cc1-4b98-971b-00f54dfe643b'], + }, + { + id: '0715b6cd-7cc1-4b98-971b-00f54dfe643b', + name: 'Create Company', + type: 'CREATE_RECORD', + valid: false, + settings: { + input: { + objectName: 'company', + objectRecord: { + name: '{{6e089bc9-aabd-435f-865f-f31c01c8f4a7.companyName}}', + domainName: { + primaryLinkUrl: + '{{6e089bc9-aabd-435f-865f-f31c01c8f4a7.companyDomain}}', + primaryLinkLabel: '', + }, + }, + }, + outputSchema: { + object: { + icon: 'IconBuildingSkyscraper', + label: 'Company', + value: 'A company', + isLeaf: true, + fieldIdName: 'id', + nameSingular: 'company', + }, + _outputSchemaType: 'RECORD', + fields: generateObjectRecordFields({ + objectMetadataInfo: { + flatObjectMetadata: companyObjectMetadata, + flatObjectMetadataMaps, + flatFieldMetadataMaps, + }, + }), + }, + errorHandlingOptions: { + retryOnFailure: { value: false }, + continueOnFailure: { value: false }, + }, + }, + __typename: 'WorkflowAction', + nextStepIds: ['6f553ea7-b00e-4371-9d88-d8298568a246'], + }, + { + id: '6f553ea7-b00e-4371-9d88-d8298568a246', + name: 'Create Person', + type: 'CREATE_RECORD', + valid: false, + settings: { + input: { + objectName: 'person', + objectRecord: { + name: { + lastName: + '{{6e089bc9-aabd-435f-865f-f31c01c8f4a7.lastName}}', + firstName: + '{{6e089bc9-aabd-435f-865f-f31c01c8f4a7.firstName}}', + }, + emails: { + primaryEmail: + '{{6e089bc9-aabd-435f-865f-f31c01c8f4a7.email}}', + additionalEmails: [], + }, + companyId: '{{0715b6cd-7cc1-4b98-971b-00f54dfe643b.id}}', + }, + }, + outputSchema: { + fields: generateObjectRecordFields({ + objectMetadataInfo: { + flatObjectMetadata: personObjectMetadata, + flatObjectMetadataMaps, + flatFieldMetadataMaps, + }, + }), + }, + errorHandlingOptions: { + retryOnFailure: { value: false }, + continueOnFailure: { value: false }, + }, + }, + __typename: 'WorkflowAction', + nextStepIds: null, + }, + ]), + status: 'ACTIVE', + position: 1, + workflowId: QUICK_LEAD_WORKFLOW_ID, + }, + { + id: CREATE_COMPANY_WHEN_ADDING_NEW_PERSON_WORKFLOW_VERSION_ID, + name: 'v1', + trigger: JSON.stringify({ + name: 'Record is created or updated', + type: 'DATABASE_EVENT', + settings: { + eventName: 'person.upserted', + fields: ['emails'], + outputSchema: generateFakeObjectRecordEvent( + { + flatObjectMetadata: personObjectMetadata, + flatObjectMetadataMaps, + flatFieldMetadataMaps, + }, + DatabaseEventAction.UPSERTED, + ), + }, + nextStepIds: ['c30d7cbe-00e0-4966-bc1a-99b0a11a2cca'], + }), + steps: JSON.stringify([ + { + id: 'c30d7cbe-00e0-4966-bc1a-99b0a11a2cca', + name: 'Is this a personal email?', + type: 'CODE', + valid: false, + position: { + x: 227.25, + y: 130, + }, + settings: { + input: { + logicFunctionId: isPersonalEmailLogicFunctionId, + logicFunctionInput: { + primaryEmail: + '{{trigger.properties.after.emails.primaryEmail}}', + }, + }, + outputSchema: { + isPersonal: { + type: 'boolean', + label: 'isPersonal', + value: true, + isLeaf: true, + }, + }, + errorHandlingOptions: { + retryOnFailure: { + value: false, + }, + continueOnFailure: { + value: false, + }, + }, + }, + __typename: 'WorkflowAction', + nextStepIds: ['01f3db05-aae5-4e4b-b361-96684f09c704'], + }, + { + id: '01f3db05-aae5-4e4b-b361-96684f09c704', + name: 'If business email', + type: 'FILTER', + valid: false, + position: { + x: 249.25, + y: 260, + }, + settings: { + input: { + stepFilters: [ + { + id: '0e595385-9e18-4869-abfd-cf72952b124c', + type: 'boolean', + value: 'false', + operand: 'IS', + isFullRecord: false, + stepOutputKey: + '{{c30d7cbe-00e0-4966-bc1a-99b0a11a2cca.isPersonal}}', + stepFilterGroupId: '5204d5f5-7b23-428c-9f84-c37971d497d3', + positionInStepFilterGroup: 0, + }, + ], + stepFilterGroups: [ + { + id: '5204d5f5-7b23-428c-9f84-c37971d497d3', + logicalOperator: 'AND', + }, + ], + }, + outputSchema: {}, + errorHandlingOptions: { + retryOnFailure: { + value: false, + }, + continueOnFailure: { + value: false, + }, + }, + }, + __typename: 'WorkflowAction', + nextStepIds: ['1b01193b-8300-4d79-940b-44464bf45505'], + }, + { + id: '1b01193b-8300-4d79-940b-44464bf45505', + name: 'Extract domain from email', + type: 'CODE', + valid: false, + position: { + x: 219.75, + y: 390, + }, + settings: { + input: { + logicFunctionId: extractDomainLogicFunctionId, + logicFunctionInput: { + email: '{{trigger.properties.after.emails.primaryEmail}}', + }, + }, + outputSchema: { + url: { + icon: 'IconVariable', + type: 'string', + label: 'url', + value: 'https://twenty.com', + isLeaf: true, + }, + domain: { + icon: 'IconVariable', + type: 'string', + label: 'domain', + value: 'twenty.com', + isLeaf: true, + }, + }, + errorHandlingOptions: { + retryOnFailure: { + value: false, + }, + continueOnFailure: { + value: false, + }, + }, + }, + __typename: 'WorkflowAction', + nextStepIds: ['becb3acf-79bb-4672-8a42-3696e94957b5'], + }, + { + id: 'becb3acf-79bb-4672-8a42-3696e94957b5', + name: 'Search Company', + type: 'FIND_RECORDS', + valid: false, + position: { + x: 247.75, + y: 520, + }, + settings: { + input: { + limit: 25, + filter: { + recordFilters: [ + { + id: 'a9b917a0-5c4c-4e8f-bf91-160d0b888693', + type: 'LINKS', + label: 'Domain Name', + value: '{{1b01193b-8300-4d79-940b-44464bf45505.domain}}', + operand: 'CONTAINS', + displayValue: + '{{1b01193b-8300-4d79-940b-44464bf45505.domain}}', + subFieldName: 'primaryLinkUrl', + fieldMetadataId: companyDomainNameFieldMetadata.id, + recordFilterGroupId: + '194e151c-cf46-4e8f-a48b-649c36082dfa', + }, + ], + recordFilterGroups: [ + { + id: '194e151c-cf46-4e8f-a48b-649c36082dfa', + logicalOperator: 'AND', + }, + ], + }, + objectName: 'company', + }, + outputSchema: {}, + errorHandlingOptions: { + retryOnFailure: { + value: false, + }, + continueOnFailure: { + value: false, + }, + }, + }, + __typename: 'WorkflowAction', + nextStepIds: ['9d0b6ef2-aad2-4853-92e1-95f2abf10d5b'], + }, + { + id: '9d0b6ef2-aad2-4853-92e1-95f2abf10d5b', + name: 'Find exact company match', + type: 'CODE', + valid: false, + position: { + x: 247.75, + y: 650, + }, + settings: { + input: { + logicFunctionId: findMatchingCompanyByDomainLogicFunctionId, + logicFunctionInput: { + companies: '{{becb3acf-79bb-4672-8a42-3696e94957b5.all}}', + domain: '{{1b01193b-8300-4d79-940b-44464bf45505.domain}}', + }, + }, + outputSchema: { + companyId: { + type: 'string', + label: 'companyId', + value: '00000000-0000-0000-0000-000000000000', + isLeaf: true, + }, + hasMatch: { + type: 'boolean', + label: 'hasMatch', + value: true, + isLeaf: true, + }, + }, + errorHandlingOptions: { + retryOnFailure: { + value: false, + }, + continueOnFailure: { + value: false, + }, + }, + }, + __typename: 'WorkflowAction', + nextStepIds: ['0c99a900-656a-40e8-977e-5a7357be33b9'], + }, + { + id: '0c99a900-656a-40e8-977e-5a7357be33b9', + name: 'If a company already exists', + type: 'IF_ELSE', + valid: false, + position: { + x: 216.25, + y: 780, + }, + settings: { + input: { + branches: [ + { + id: '1344c151-15ff-40e2-a1a3-925fabaf5b1c', + nextStepIds: ['ffdd4271-75d4-4805-b1f8-2167a113c3b2'], + filterGroupId: 'f5c41047-2a6e-49fb-968a-fa7789a90ee5', + }, + { + id: 'fe6dd152-1103-4324-af51-3ff994d1f8a7', + nextStepIds: ['ddafb9db-a94f-40b9-a5c9-becce857edf7'], + }, + ], + stepFilters: [ + { + id: '290cc6a3-08fd-4be5-b42e-966d0bb90ff7', + type: 'boolean', + value: 'true', + operand: 'IS', + isFullRecord: false, + stepOutputKey: + '{{9d0b6ef2-aad2-4853-92e1-95f2abf10d5b.hasMatch}}', + stepFilterGroupId: 'f5c41047-2a6e-49fb-968a-fa7789a90ee5', + positionInStepFilterGroup: 0, + }, + ], + stepFilterGroups: [ + { + id: 'f5c41047-2a6e-49fb-968a-fa7789a90ee5', + logicalOperator: 'AND', + }, + ], + }, + outputSchema: {}, + errorHandlingOptions: { + retryOnFailure: { + value: false, + }, + continueOnFailure: { + value: false, + }, + }, + }, + __typename: 'WorkflowAction', + }, + { + id: 'ffdd4271-75d4-4805-b1f8-2167a113c3b2', + name: 'Attach person to existing company', + type: 'UPDATE_RECORD', + valid: false, + position: { + x: 0, + y: 910, + }, + settings: { + input: { + objectName: 'person', + objectRecord: { + companyId: + '{{9d0b6ef2-aad2-4853-92e1-95f2abf10d5b.companyId}}', + }, + fieldsToUpdate: ['companyId'], + objectRecordId: '{{trigger.properties.after.id}}', + }, + outputSchema: {}, + errorHandlingOptions: { + retryOnFailure: { + value: false, + }, + continueOnFailure: { + value: false, + }, + }, + }, + __typename: 'WorkflowAction', + }, + { + id: 'ddafb9db-a94f-40b9-a5c9-becce857edf7', + name: 'Create a new company', + type: 'CREATE_RECORD', + valid: false, + position: { + x: 440, + y: 910, + }, + settings: { + input: { + objectName: 'company', + objectRecord: { + name: '{{1b01193b-8300-4d79-940b-44464bf45505.domain}}', + domainName: { + primaryLinkUrl: + '{{1b01193b-8300-4d79-940b-44464bf45505.url}}', + primaryLinkLabel: + '{{1b01193b-8300-4d79-940b-44464bf45505.domain}}', + }, + }, + }, + outputSchema: {}, + errorHandlingOptions: { + retryOnFailure: { + value: false, + }, + continueOnFailure: { + value: false, + }, + }, + }, + __typename: 'WorkflowAction', + nextStepIds: ['d5d5d6e1-391f-4142-83c1-670f7087f079'], + }, + { + id: 'd5d5d6e1-391f-4142-83c1-670f7087f079', + name: 'Attach person to this company', + type: 'UPDATE_RECORD', + valid: false, + position: { + x: 420.5, + y: 1040, + }, + settings: { + input: { + objectName: 'person', + objectRecord: { + companyId: '{{ddafb9db-a94f-40b9-a5c9-becce857edf7.id}}', + }, + fieldsToUpdate: ['companyId'], + objectRecordId: '{{trigger.properties.after.id}}', + }, + outputSchema: {}, + errorHandlingOptions: { + retryOnFailure: { + value: false, + }, + continueOnFailure: { + value: false, + }, + }, + }, + __typename: 'WorkflowAction', + }, + ]), + status: 'ACTIVE', + position: 2, + workflowId: CREATE_COMPANY_WHEN_ADDING_NEW_PERSON_WORKFLOW_ID, + }, + ]) + .returning('*') + .execute(); + + await entityManager + .createQueryBuilder() + .insert() + .into(`${schemaName}.workflowAutomatedTrigger`, [ + 'id', + 'workflowId', + 'type', + 'settings', + ]) + .orIgnore() + .values([ + { + id: CREATE_COMPANY_WHEN_ADDING_NEW_PERSON_AUTOMATED_TRIGGER_ID, + workflowId: CREATE_COMPANY_WHEN_ADDING_NEW_PERSON_WORKFLOW_ID, + type: 'DATABASE_EVENT', + settings: { + eventName: 'person.upserted', + fields: ['emails'], + }, + }, + ]) + .execute(); +};