diff --git a/packages/twenty-server/src/database/commands/database-command.module.ts b/packages/twenty-server/src/database/commands/database-command.module.ts index 8422f1e9cfd7..f6f95ce43620 100644 --- a/packages/twenty-server/src/database/commands/database-command.module.ts +++ b/packages/twenty-server/src/database/commands/database-command.module.ts @@ -8,6 +8,7 @@ import { DataSeedDemoWorkspaceModule } from 'src/database/commands/data-seed-dem import { DataSeedWorkspaceCommand } from 'src/database/commands/data-seed-dev-workspace.command'; import { ConfirmationQuestion } from 'src/database/commands/questions/confirmation.question'; import { UpgradeTo0_40CommandModule } from 'src/database/commands/upgrade-version/0-40/0-40-upgrade-version.module'; +import { UpgradeTo0_41CommandModule } from 'src/database/commands/upgrade-version/0-41/0-41-upgrade-version.module'; import { TypeORMModule } from 'src/database/typeorm/typeorm.module'; import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity'; import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; @@ -49,6 +50,7 @@ import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/worksp WorkspaceCacheStorageModule, WorkspaceMetadataVersionModule, UpgradeTo0_40CommandModule, + UpgradeTo0_41CommandModule, FeatureFlagModule, ], providers: [ diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-41/0-41-add-tasks-assigned-to-me-view.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-41/0-41-add-tasks-assigned-to-me-view.command.ts new file mode 100644 index 000000000000..00f0bbf02368 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version/0-41/0-41-add-tasks-assigned-to-me-view.command.ts @@ -0,0 +1,181 @@ +import { InjectRepository } from '@nestjs/typeorm'; + +import chalk from 'chalk'; +import { Command } from 'nest-commander'; +import { Repository } from 'typeorm'; +import { v4 } from 'uuid'; + +import { + ActiveWorkspacesCommandOptions, + ActiveWorkspacesCommandRunner, +} from 'src/database/commands/active-workspaces.command'; +import { isCommandLogger } from 'src/database/commands/logger'; +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 { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; +import { tasksAssignedToMeView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/tasks-assigned-to-me'; +import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service'; +import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; +import { ViewFieldWorkspaceEntity } from 'src/modules/view/standard-objects/view-field.workspace-entity'; +import { ViewFilterWorkspaceEntity } from 'src/modules/view/standard-objects/view-filter.workspace-entity'; +import { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity'; + +@Command({ + name: 'upgrade-0.41:add-tasks-assigned-to-me-view', + description: 'Add tasks assigned to me view', +}) +export class AddTasksAssignedToMeViewCommand extends ActiveWorkspacesCommandRunner { + 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, + private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService, + private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService, + ) { + super(workspaceRepository); + } + + async executeActiveWorkspacesCommand( + _passedParam: string[], + options: ActiveWorkspacesCommandOptions, + workspaceIds: string[], + ): Promise { + this.logger.log('Running command to add tasks assigned to me view'); + + if (isCommandLogger(this.logger)) { + this.logger.setVerbose(options.verbose ?? false); + } + + let workspaceIterator = 1; + + for (const workspaceId of workspaceIds) { + this.logger.log( + `Running command for workspace ${workspaceId} ${workspaceIterator}/${workspaceIds.length}`, + ); + + try { + const objectMetadata = await this.objectMetadataRepository.find({ + where: { workspaceId }, + relations: ['fields'], + }); + + const objectMetadataMap = objectMetadata.reduce((acc, object) => { + acc[object.standardId ?? ''] = { + id: object.id, + fields: object.fields.reduce((acc, field) => { + acc[field.standardId ?? ''] = field.id; + + return acc; + }, {}), + }; + + return acc; + }, {}); + + const taskObjectMetadata = objectMetadata.find( + (object) => object.standardId === STANDARD_OBJECT_IDS.task, + ); + + if (!taskObjectMetadata) { + this.logger.warn( + `Task object not found for workspace ${workspaceId}`, + ); + workspaceIterator++; + continue; + } + + const viewRepository = + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, + 'view', + false, + ); + + const existingView = await viewRepository.findOne({ + where: { + name: 'Assigned to Me', + objectMetadataId: taskObjectMetadata.id, + }, + }); + + if (existingView) { + this.logger.log( + `"Assigned to Me" view already exists for workspace ${workspaceId}`, + ); + workspaceIterator++; + continue; + } + + const viewDefinition = tasksAssignedToMeView(objectMetadataMap); + const viewId = v4(); + + const insertedView = await viewRepository.save({ + id: viewId, + name: viewDefinition.name, + objectMetadataId: viewDefinition.objectMetadataId, + type: viewDefinition.type, + position: viewDefinition.position, + icon: viewDefinition.icon, + kanbanFieldMetadataId: viewDefinition.kanbanFieldMetadataId, + }); + + if (viewDefinition.fields && viewDefinition.fields.length > 0) { + const viewFieldRepository = + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, + 'viewField', + false, + ); + + const viewFields = viewDefinition.fields.map((field) => ({ + fieldMetadataId: field.fieldMetadataId, + position: field.position, + isVisible: field.isVisible, + size: field.size, + viewId: insertedView.id, + })); + + await viewFieldRepository.save(viewFields); + } + + if (viewDefinition.filters && viewDefinition.filters.length > 0) { + const viewFilterRepository = + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, + 'viewFilter', + false, + ); + + const viewFilters = viewDefinition.filters.map((filter) => ({ + fieldMetadataId: filter.fieldMetadataId, + displayValue: filter.displayValue, + operand: filter.operand, + value: filter.value, + viewId: insertedView.id, + })); + + await viewFilterRepository.save(viewFilters); + } + + await this.workspaceMetadataVersionService.incrementMetadataVersion( + workspaceId, + ); + + workspaceIterator++; + this.logger.log( + chalk.green(`Command completed for workspace ${workspaceId}.`), + ); + } catch { + this.logger.log(chalk.red(`Error in workspace ${workspaceId}.`)); + workspaceIterator++; + } + } + this.logger.log(chalk.green(`Command completed!`)); + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-41/0-41-upgrade-version.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-41/0-41-upgrade-version.command.ts new file mode 100644 index 000000000000..f3a4f4579418 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version/0-41/0-41-upgrade-version.command.ts @@ -0,0 +1,37 @@ +import { InjectRepository } from '@nestjs/typeorm'; + +import { Command } from 'nest-commander'; +import { Repository } from 'typeorm'; + +import { ActiveWorkspacesCommandRunner } from 'src/database/commands/active-workspaces.command'; +import { BaseCommandOptions } from 'src/database/commands/base.command'; +import { AddTasksAssignedToMeViewCommand } from 'src/database/commands/upgrade-version/0-41/0-41-add-tasks-assigned-to-me-view.command'; +import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; + +@Command({ + name: 'upgrade-0.41', + description: 'Upgrade to 0.41', +}) +export class UpgradeTo0_41Command extends ActiveWorkspacesCommandRunner { + constructor( + @InjectRepository(Workspace, 'core') + protected readonly workspaceRepository: Repository, + private readonly addTasksAssignedToMeViewCommand: AddTasksAssignedToMeViewCommand, + ) { + super(workspaceRepository); + } + + async executeActiveWorkspacesCommand( + passedParam: string[], + options: BaseCommandOptions, + workspaceIds: string[], + ): Promise { + this.logger.log('Running command to upgrade to 0.41'); + + await this.addTasksAssignedToMeViewCommand.executeActiveWorkspacesCommand( + passedParam, + options, + workspaceIds, + ); + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-41/0-41-upgrade-version.module.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-41/0-41-upgrade-version.module.ts new file mode 100644 index 000000000000..db05994cc257 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version/0-41/0-41-upgrade-version.module.ts @@ -0,0 +1,26 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { AddTasksAssignedToMeViewCommand } from 'src/database/commands/upgrade-version/0-41/0-41-add-tasks-assigned-to-me-view.command'; +import { UpgradeTo0_41Command } from 'src/database/commands/upgrade-version/0-41/0-41-upgrade-version.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 { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module'; +import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module'; +import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.module'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([Workspace], 'core'), + TypeOrmModule.forFeature( + [ObjectMetadataEntity, FieldMetadataEntity], + 'metadata', + ), + WorkspaceMigrationRunnerModule, + WorkspaceMigrationModule, + WorkspaceMetadataVersionModule, + ], + providers: [UpgradeTo0_41Command, AddTasksAssignedToMeViewCommand], +}) +export class UpgradeTo0_41CommandModule {} diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/seed-view-with-demo-data.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/seed-view-with-demo-data.ts index ed8bdbab1c09..5a972d7b5676 100644 --- a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/seed-view-with-demo-data.ts +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/seed-view-with-demo-data.ts @@ -9,6 +9,7 @@ import { opportunitiesAllView } from 'src/engine/workspace-manager/standard-obje import { opportunitiesByStageView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/opportunity-by-stage.view'; import { peopleAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/people-all.view'; import { tasksAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/tasks-all.view'; +import { tasksAssignedToMeView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/tasks-assigned-to-me'; import { tasksByStatusView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/tasks-by-status.view'; import { workflowRunsAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/workflow-runs-all.view'; import { workflowVersionsAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/workflow-versions-all.view'; @@ -27,6 +28,7 @@ export const seedViewWithDemoData = async ( opportunitiesByStageView(objectMetadataStandardIdToIdMap), notesAllView(objectMetadataStandardIdToIdMap), tasksAllView(objectMetadataStandardIdToIdMap), + tasksAssignedToMeView(objectMetadataStandardIdToIdMap), tasksByStatusView(objectMetadataStandardIdToIdMap), ...(isWorkflowEnabled ? [ diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/tasks-assigned-to-me.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/tasks-assigned-to-me.ts new file mode 100644 index 000000000000..aa4d8dfcd9ad --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/tasks-assigned-to-me.ts @@ -0,0 +1,110 @@ +import { ObjectMetadataStandardIdToIdMap } from 'src/engine/metadata-modules/object-metadata/interfaces/object-metadata-standard-id-to-id-map'; + +import { + BASE_OBJECT_STANDARD_FIELD_IDS, + TASK_STANDARD_FIELD_IDS, +} from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; +import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; + +export const tasksAssignedToMeView = ( + objectMetadataStandardIdToIdMap: ObjectMetadataStandardIdToIdMap, +) => { + return { + name: 'Assigned to Me', + objectMetadataId: + objectMetadataStandardIdToIdMap[STANDARD_OBJECT_IDS.task].id, + type: 'table', + key: null, + position: 2, + icon: 'IconUserCircle', + kanbanFieldMetadataId: '', + filters: [ + { + fieldMetadataId: + objectMetadataStandardIdToIdMap[STANDARD_OBJECT_IDS.task].fields[ + TASK_STANDARD_FIELD_IDS.assignee + ], + displayValue: 'Me', + operand: 'is', + value: JSON.stringify({ + isCurrentWorkspaceMemberSelected: true, + selectedRecordIds: [], + }), + }, + ], + fields: [ + { + fieldMetadataId: + objectMetadataStandardIdToIdMap[STANDARD_OBJECT_IDS.task].fields[ + TASK_STANDARD_FIELD_IDS.title + ], + position: 0, + isVisible: true, + size: 210, + }, + { + fieldMetadataId: + objectMetadataStandardIdToIdMap[STANDARD_OBJECT_IDS.task].fields[ + TASK_STANDARD_FIELD_IDS.status + ], + position: 2, + isVisible: true, + size: 150, + }, + { + fieldMetadataId: + objectMetadataStandardIdToIdMap[STANDARD_OBJECT_IDS.task].fields[ + TASK_STANDARD_FIELD_IDS.taskTargets + ], + position: 3, + isVisible: true, + size: 150, + }, + { + fieldMetadataId: + objectMetadataStandardIdToIdMap[STANDARD_OBJECT_IDS.task].fields[ + TASK_STANDARD_FIELD_IDS.createdBy + ], + position: 4, + isVisible: true, + size: 150, + }, + { + fieldMetadataId: + objectMetadataStandardIdToIdMap[STANDARD_OBJECT_IDS.task].fields[ + TASK_STANDARD_FIELD_IDS.dueAt + ], + position: 5, + isVisible: true, + size: 150, + }, + { + fieldMetadataId: + objectMetadataStandardIdToIdMap[STANDARD_OBJECT_IDS.task].fields[ + TASK_STANDARD_FIELD_IDS.assignee + ], + position: 6, + isVisible: true, + size: 150, + }, + { + fieldMetadataId: + objectMetadataStandardIdToIdMap[STANDARD_OBJECT_IDS.task].fields[ + TASK_STANDARD_FIELD_IDS.body + ], + position: 7, + isVisible: true, + size: 150, + }, + { + fieldMetadataId: + objectMetadataStandardIdToIdMap[STANDARD_OBJECT_IDS.task].fields[ + BASE_OBJECT_STANDARD_FIELD_IDS.createdAt + ], + position: 8, + isVisible: true, + size: 150, + }, + ], + }; +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/tasks-by-status.view.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/tasks-by-status.view.ts index db0461f9a63a..2bfca9245d74 100644 --- a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/tasks-by-status.view.ts +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/tasks-by-status.view.ts @@ -10,12 +10,12 @@ export const tasksByStatusView = ( objectMetadataStandardIdToIdMap: ObjectMetadataStandardIdToIdMap, ) => { return { - name: 'By status', + name: 'By Status', objectMetadataId: objectMetadataStandardIdToIdMap[STANDARD_OBJECT_IDS.task].id, type: 'kanban', key: null, - position: 0, + position: 1, icon: 'IconLayoutKanban', kanbanFieldMetadataId: objectMetadataStandardIdToIdMap[STANDARD_OBJECT_IDS.task].fields[