From 0258f0f3571c83147a56a7841c1cd002fbdff6b6 Mon Sep 17 00:00:00 2001 From: Thomas Trompette Date: Fri, 8 Nov 2024 10:14:56 +0100 Subject: [PATCH 1/2] Create workflow setup command --- .../workflow-actions/code.workflow-action.ts | 16 +- .../commands/seed-workflow-views.command.ts | 175 ++++++++++++++++++ .../commands/workflow-command.module.ts | 20 ++ .../workflow/common/workflow-common.module.ts | 3 +- 4 files changed, 205 insertions(+), 9 deletions(-) create mode 100644 packages/twenty-server/src/modules/workflow/common/commands/seed-workflow-views.command.ts create mode 100644 packages/twenty-server/src/modules/workflow/common/commands/workflow-command.module.ts diff --git a/packages/twenty-server/src/modules/serverless/workflow-actions/code.workflow-action.ts b/packages/twenty-server/src/modules/serverless/workflow-actions/code.workflow-action.ts index 86adf74f89c4..39a66a87b289 100644 --- a/packages/twenty-server/src/modules/serverless/workflow-actions/code.workflow-action.ts +++ b/packages/twenty-server/src/modules/serverless/workflow-actions/code.workflow-action.ts @@ -21,16 +21,16 @@ export class CodeWorkflowAction implements WorkflowAction { async execute( workflowStepInput: WorkflowCodeStepInput, ): Promise { - const { workspaceId } = this.scopedWorkspaceContextFactory.create(); + try { + const { workspaceId } = this.scopedWorkspaceContextFactory.create(); - if (!workspaceId) { - throw new WorkflowStepExecutorException( - 'Scoped workspace not found', - WorkflowStepExecutorExceptionCode.SCOPED_WORKSPACE_NOT_FOUND, - ); - } + if (!workspaceId) { + throw new WorkflowStepExecutorException( + 'Scoped workspace not found', + WorkflowStepExecutorExceptionCode.SCOPED_WORKSPACE_NOT_FOUND, + ); + } - try { const result = await this.serverlessFunctionService.executeOneServerlessFunction( workflowStepInput.serverlessFunctionId, diff --git a/packages/twenty-server/src/modules/workflow/common/commands/seed-workflow-views.command.ts b/packages/twenty-server/src/modules/workflow/common/commands/seed-workflow-views.command.ts new file mode 100644 index 000000000000..e00347284d2a --- /dev/null +++ b/packages/twenty-server/src/modules/workflow/common/commands/seed-workflow-views.command.ts @@ -0,0 +1,175 @@ +import { Logger } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; + +import { Command } from 'nest-commander'; +import { Repository } from 'typeorm'; +import { v4 } from 'uuid'; + +import { + ActiveWorkspacesCommandOptions, + ActiveWorkspacesCommandRunner, +} from 'src/database/commands/active-workspaces.command'; +import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; +import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; + +@Command({ + name: 'workflow:seed:views', + description: 'Seed workflow views for workspace.', +}) +export class SeedWorkflowViewsCommand extends ActiveWorkspacesCommandRunner { + protected readonly logger: Logger; + + constructor( + @InjectRepository(Workspace, 'core') + protected readonly workspaceRepository: Repository, + @InjectRepository(ObjectMetadataEntity, 'metadata') + private readonly objectMetadataRepository: Repository, + + @InjectRepository(FieldMetadataEntity, 'metadata') + private readonly fieldMetadataRepository: Repository, + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + ) { + super(workspaceRepository); + this.logger = new Logger(this.constructor.name); + } + + async executeActiveWorkspacesCommand( + _passedParam: string[], + _options: ActiveWorkspacesCommandOptions, + _workspaceIds: string[], + ): Promise { + const { dryRun } = _options; + + for (const workspaceId of _workspaceIds) { + await this.execute(workspaceId, dryRun); + } + } + + private async execute(workspaceId: string, dryRun = false): Promise { + this.logger.log(`Seeding workflow views for workspace: ${workspaceId}`); + + const workflowViewId = await this.seedView( + workspaceId, + 'workflow', + 'All Workflows', + ); + + await this.seedView( + workspaceId, + 'workflowVersion', + 'All Workflow Versions', + ); + + await this.seedView(workspaceId, 'workflowRun', 'All Workflow Runs'); + + const favoriteRepository = + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, + 'favorite', + ); + + const existingFavorites = await favoriteRepository.find({ + where: { + viewId: workflowViewId, + }, + }); + + if (existingFavorites.length > 0) { + this.logger.log( + `Favorite already exists for view: ${existingFavorites[0].id}`, + ); + + return; + } + + if (dryRun) { + this.logger.log(`Dry run: Creating favorite for view: ${workflowViewId}`); + + return; + } + + await favoriteRepository.insert({ + viewId: workflowViewId, + position: 5, + }); + } + + private async seedView( + workspaceId: string, + nameSingular: string, + viewName: string, + dryRun = false, + ): Promise { + const objectMetadata = ( + await this.objectMetadataRepository.find({ + where: { workspaceId, nameSingular }, + }) + )[0]; + + const fieldMetadataName = ( + await this.fieldMetadataRepository.find({ + where: { + workspaceId, + objectMetadataId: objectMetadata.id, + name: 'name', + }, + }) + )[0]; + + const viewRepository = + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, + 'view', + ); + + const viewFieldRepository = + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, + 'viewField', + ); + + const viewId = v4(); + + const existingViews = await viewRepository.find({ + where: { + objectMetadataId: objectMetadata.id, + name: viewName, + }, + }); + + if (existingViews.length > 0) { + this.logger.log(`View already exists: ${existingViews[0].id}`); + + return existingViews[0].id; + } + + if (dryRun) { + this.logger.log(`Dry run: Creating view: ${viewName}`); + + return viewId; + } + + await viewRepository.insert({ + id: viewId, + name: viewName, + objectMetadataId: objectMetadata.id, + type: 'table', + key: 'INDEX', + position: 0, + icon: 'IconSettingsAutomation', + kanbanFieldMetadataId: '', + }); + + await viewFieldRepository.insert({ + fieldMetadataId: fieldMetadataName.id, + position: 0, + isVisible: true, + size: 210, + viewId: viewId, + }); + + return viewId; + } +} diff --git a/packages/twenty-server/src/modules/workflow/common/commands/workflow-command.module.ts b/packages/twenty-server/src/modules/workflow/common/commands/workflow-command.module.ts new file mode 100644 index 000000000000..08389b824d7f --- /dev/null +++ b/packages/twenty-server/src/modules/workflow/common/commands/workflow-command.module.ts @@ -0,0 +1,20 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; +import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { SeedWorkflowViewsCommand } from 'src/modules/workflow/common/commands/seed-workflow-views.command'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([Workspace], 'core'), + TypeOrmModule.forFeature( + [ObjectMetadataEntity, FieldMetadataEntity], + 'metadata', + ), + ], + providers: [SeedWorkflowViewsCommand], + exports: [SeedWorkflowViewsCommand], +}) +export class WorkflowCommandModule {} diff --git a/packages/twenty-server/src/modules/workflow/common/workflow-common.module.ts b/packages/twenty-server/src/modules/workflow/common/workflow-common.module.ts index fc23aa8ff88a..e49d85465de4 100644 --- a/packages/twenty-server/src/modules/workflow/common/workflow-common.module.ts +++ b/packages/twenty-server/src/modules/workflow/common/workflow-common.module.ts @@ -1,10 +1,11 @@ import { Module } from '@nestjs/common'; +import { WorkflowCommandModule } from 'src/modules/workflow/common/commands/workflow-command.module'; import { WorkflowQueryHookModule } from 'src/modules/workflow/common/query-hooks/workflow-query-hook.module'; import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service'; @Module({ - imports: [WorkflowQueryHookModule], + imports: [WorkflowQueryHookModule, WorkflowCommandModule], providers: [WorkflowCommonWorkspaceService], exports: [WorkflowCommonWorkspaceService], }) From dd15dfd711cc1584b41c4257927ab22a2d82bbec Mon Sep 17 00:00:00 2001 From: Thomas Trompette Date: Fri, 8 Nov 2024 11:48:10 +0100 Subject: [PATCH 2/2] Add explicit exceptions --- .../common/commands/seed-workflow-views.command.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/twenty-server/src/modules/workflow/common/commands/seed-workflow-views.command.ts b/packages/twenty-server/src/modules/workflow/common/commands/seed-workflow-views.command.ts index e00347284d2a..62e969630b25 100644 --- a/packages/twenty-server/src/modules/workflow/common/commands/seed-workflow-views.command.ts +++ b/packages/twenty-server/src/modules/workflow/common/commands/seed-workflow-views.command.ts @@ -106,7 +106,11 @@ export class SeedWorkflowViewsCommand extends ActiveWorkspacesCommandRunner { await this.objectMetadataRepository.find({ where: { workspaceId, nameSingular }, }) - )[0]; + )?.[0]; + + if (!objectMetadata) { + throw new Error(`Object metadata not found: ${nameSingular}`); + } const fieldMetadataName = ( await this.fieldMetadataRepository.find({ @@ -116,7 +120,13 @@ export class SeedWorkflowViewsCommand extends ActiveWorkspacesCommandRunner { name: 'name', }, }) - )[0]; + )?.[0]; + + if (!fieldMetadataName) { + throw new Error( + `Field metadata not found for ${objectMetadata.id}: name`, + ); + } const viewRepository = await this.twentyORMGlobalManager.getRepositoryForWorkspace(