From 4dbc78285dd965a1fd9d25042a90221739829bc1 Mon Sep 17 00:00:00 2001 From: Paul Rastoin <45004772+prastoin@users.noreply.github.com> Date: Tue, 28 Oct 2025 12:03:49 +0100 Subject: [PATCH] Remove deprecated `view` and related workspace entities (#15393) # Introduction A while ago we migrated view from workspace to metadata Their standard objects workspace entities declaration remained we can now remove them ## Deprecating commands before 1.5 The view migration command from workspace to metadata was introduced in `1.5.0`. Removing the `baseWorkspaceEntity` make this command obsolete. If tomorrow twenty handles auto upgrade in latest and a user having an instance in `1.3.0` starts auto-upgrading he won't be able to migrate his views ( that's why we should not support upgrade before 1.5 anymore here ) We will have the same use case with FavoritesFolders --- .../0-54-clean-not-found-files.command.ts | 225 ------ .../0-54-created-by-default-value.command.ts | 98 --- ...standard-select-fields-position.command.ts | 107 --- ...case-user-and-invitation-emails.command.ts | 113 --- ...lt-avatar-url-to-user-workspace.command.ts | 87 --- .../0-54-upgrade-version-command.module.ts | 51 -- ...0-55-deduplicate-indexed-fields.command.ts | 298 -------- .../0-55-upgrade-version-command.module.ts | 32 - ...enqueued-status-to-workflow-run.command.ts | 111 --- .../1-1/1-1-fix-schema-array-type.command.ts | 98 --- ...field-is-label-synced-with-name.command.ts | 74 -- .../1-1-migrate-workflow-run-state.command.ts | 155 ---- .../1-1/1-1-upgrade-version-command.module.ts | 57 -- ...ueued-status-to-workflow-run-v2.command.ts | 132 ---- ...ds-to-workflow-version-triggers.command.ts | 97 --- ...ove-workflow-runs-without-state.command.ts | 39 - .../1-2/1-2-upgrade-version-command.module.ts | 32 - ...ep-ids-to-workflow-runs-trigger.command.ts | 90 --- ...column-type-in-workspace-schema.command.ts | 58 -- .../1-3/1-3-upgrade-version-command.module.ts | 47 -- ...flow-versions-and-workflow-runs.command.ts | 333 --------- .../1-5/1-5-migrate-views-to-core.command.ts | 671 ------------------ ...5-remove-favorite-view-relation.command.ts | 151 ---- .../1-5/1-5-upgrade-version-command.module.ts | 36 - .../upgrade-version-command.module.ts | 14 +- .../upgrade.command.ts | 131 ---- .../types/view-open-record-in-type.type.ts | 4 + .../prefill-core-views.ts | 2 +- .../types/view-definition.interface.ts | 2 +- .../views/dashboards-all.view.ts | 2 +- .../views/workflow-runs-all.view.ts | 2 +- .../views/workflow-versions-all.view.ts | 2 +- .../views/workflows-all.view.ts | 2 +- .../standard-objects/index.ts | 12 - .../src/modules/modules.module.ts | 2 - .../view-field.workspace-entity.ts | 181 ----- .../view-filter-group.workspace-entity.ts | 96 --- .../view-filter.workspace-entity.ts | 108 --- .../view-group.workspace-entity.ts | 82 --- .../view-sort.workspace-entity.ts | 68 -- .../standard-objects/view.workspace-entity.ts | 307 -------- .../src/modules/view/standard-view.module.ts | 30 - ...ew-filter-workspace-value-to-core-value.ts | 11 - .../src/modules/view/views.exception.ts | 7 - .../group-by-resolver.integration-spec.ts | 2 +- .../view-fields.integration-spec.ts | 61 -- .../view-filters.integration-spec.ts | 61 -- .../view-sorts.integration-spec.ts | 57 -- .../views.integration-spec.ts | 67 -- ...cessful-find-view.integration-spec.ts.snap | 114 +++ .../successful-find-view.integration-spec.ts | 78 ++ .../test/integration/utils/view-test.util.ts | 2 +- 52 files changed, 205 insertions(+), 4494 deletions(-) delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/0-54/0-54-clean-not-found-files.command.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/0-54/0-54-created-by-default-value.command.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/0-54/0-54-fix-standard-select-fields-position.command.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/0-54/0-54-lowercase-user-and-invitation-emails.command.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/0-54/0-54-migrate-default-avatar-url-to-user-workspace.command.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/0-54/0-54-upgrade-version-command.module.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/0-55/0-55-deduplicate-indexed-fields.command.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/0-55/0-55-upgrade-version-command.module.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/1-1/1-1-add-enqueued-status-to-workflow-run.command.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/1-1/1-1-fix-schema-array-type.command.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/1-1/1-1-fix-update-standard-field-is-label-synced-with-name.command.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/1-1/1-1-migrate-workflow-run-state.command.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/1-1/1-1-upgrade-version-command.module.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/1-2/1-2-add-enqueued-status-to-workflow-run-v2.command.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/1-2/1-2-add-next-step-ids-to-workflow-version-triggers.command.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/1-2/1-2-remove-workflow-runs-without-state.command.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/1-2/1-2-upgrade-version-command.module.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/1-3/1-3-add-next-step-ids-to-workflow-runs-trigger.command.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/1-3/1-3-update-timestamp-column-type-in-workspace-schema.command.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/1-3/1-3-upgrade-version-command.module.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/1-5/1-5-add-positions-to-workflow-versions-and-workflow-runs.command.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/1-5/1-5-migrate-views-to-core.command.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/1-5/1-5-remove-favorite-view-relation.command.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/1-5/1-5-upgrade-version-command.module.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/view/types/view-open-record-in-type.type.ts delete mode 100644 packages/twenty-server/src/modules/view/standard-objects/view-field.workspace-entity.ts delete mode 100644 packages/twenty-server/src/modules/view/standard-objects/view-filter-group.workspace-entity.ts delete mode 100644 packages/twenty-server/src/modules/view/standard-objects/view-filter.workspace-entity.ts delete mode 100644 packages/twenty-server/src/modules/view/standard-objects/view-group.workspace-entity.ts delete mode 100644 packages/twenty-server/src/modules/view/standard-objects/view-sort.workspace-entity.ts delete mode 100644 packages/twenty-server/src/modules/view/standard-objects/view.workspace-entity.ts delete mode 100644 packages/twenty-server/src/modules/view/standard-view.module.ts delete mode 100644 packages/twenty-server/src/modules/view/utils/convert-view-filter-workspace-value-to-core-value.ts delete mode 100644 packages/twenty-server/src/modules/view/views.exception.ts delete mode 100644 packages/twenty-server/test/integration/graphql/suites/object-generated/view-fields.integration-spec.ts delete mode 100644 packages/twenty-server/test/integration/graphql/suites/object-generated/view-filters.integration-spec.ts delete mode 100644 packages/twenty-server/test/integration/graphql/suites/object-generated/view-sorts.integration-spec.ts delete mode 100644 packages/twenty-server/test/integration/graphql/suites/object-generated/views.integration-spec.ts create mode 100644 packages/twenty-server/test/integration/graphql/suites/view/__snapshots__/successful-find-view.integration-spec.ts.snap create mode 100644 packages/twenty-server/test/integration/graphql/suites/view/successful-find-view.integration-spec.ts diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/0-54/0-54-clean-not-found-files.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/0-54/0-54-clean-not-found-files.command.ts deleted file mode 100644 index e85a255620..0000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/0-54/0-54-clean-not-found-files.command.ts +++ /dev/null @@ -1,225 +0,0 @@ -import { InjectRepository } from '@nestjs/typeorm'; - -import { isNonEmptyString } from '@sniptt/guards'; -import { Command } from 'nest-commander'; -import { Equal, Not, Repository } from 'typeorm'; - -import { - ActiveOrSuspendedWorkspacesMigrationCommandRunner, - type RunOnWorkspaceArgs, -} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; -import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service'; -import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; -import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { type AttachmentWorkspaceEntity } from 'src/modules/attachment/standard-objects/attachment.workspace-entity'; -import { type PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity'; -import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; - -@Command({ - name: 'upgrade:0-54:clean-not-found-files', - description: 'Clean not found files', -}) -export class CleanNotFoundFilesCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { - constructor( - @InjectRepository(WorkspaceEntity) - protected readonly workspaceRepository: Repository, - protected readonly twentyORMGlobalManager: TwentyORMGlobalManager, - private readonly fileStorageService: FileStorageService, - ) { - super(workspaceRepository, twentyORMGlobalManager); - } - - override async runOnWorkspace({ - index, - total, - workspaceId, - options, - }: RunOnWorkspaceArgs): Promise { - this.logger.log( - `Running command for workspace ${workspaceId} ${index + 1}/${total}`, - ); - - await this.cleanNotFoundFiles(workspaceId, !!options.dryRun); - } - - private async cleanNotFoundFiles(workspaceId: string, dryRun: boolean) { - await this.cleanWorkspaceLogo(workspaceId, dryRun); - await this.softDeleteAttachments(workspaceId, dryRun); - await this.cleanWorkspaceMembersAvatarUrl(workspaceId, dryRun); - await this.cleanPeopleAvatarUrl(workspaceId, dryRun); - } - - private async checkIfFileIsFound(path: string, workspaceId: string) { - this.logger.log(`Checking if file is found ${path}`); - if (path.startsWith('https://')) return true; // seed data - - const isFileFound = await this.fileStorageService.checkFileExists({ - folderPath: `workspace-${workspaceId}`, - filename: path, - }); - - this.logger.log(`File found: ${isFileFound}`); - - return isFileFound; - } - - private async cleanWorkspaceLogo(workspaceId: string, dryRun: boolean) { - this.logger.log(`Cleaning workspace logo for workspace ${workspaceId}`); - const workspace = await this.workspaceRepository.findOneOrFail({ - where: { - id: workspaceId, - }, - }); - - if (!isNonEmptyString(workspace.logo)) return; - - this.logger.log(`Processing workspace logo for workspace ${workspace.id}`); - - const isFileFound = await this.checkIfFileIsFound( - workspace.logo, - workspace.id, - ); - - if (isFileFound) return; - - if (!dryRun) - await this.workspaceRepository.update(workspace.id, { - logo: '', - }); - - this.logger.log( - `${dryRun ? 'Dry run - ' : ''}Set logo to '' for workspace ${workspace.id}`, - ); - } - - private async softDeleteAttachments(workspaceId: string, dryRun: boolean) { - this.logger.log(`Cleaning attachments for workspace ${workspaceId}`); - const attachmentRepository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace( - workspaceId, - 'attachment', - ); - const attachmentsCount = await attachmentRepository.count(); - const chunkSize = 10; - - const attachmentIdsToSoftDelete: string[] = []; - - for (let offset = 0; offset < attachmentsCount; offset += chunkSize) { - const attachmentsChunk = await attachmentRepository.find({ - skip: offset, - take: chunkSize, - }); - - const attachmentIdsToSoftDeleteChunk = await Promise.all( - attachmentsChunk.map(async (attachment) => { - this.logger.log(`Processing attachment ${attachment.id}`); - const isFileFound = await this.checkIfFileIsFound( - attachment.fullPath, - workspaceId, - ); - - return isFileFound ? '' : attachment.id; - }), - ); - - attachmentIdsToSoftDelete.push( - ...attachmentIdsToSoftDeleteChunk.filter(isNonEmptyString), - ); - } - - if (attachmentIdsToSoftDelete.length === 0) return; - - if (!dryRun) - await attachmentRepository.softDelete(attachmentIdsToSoftDelete); - - this.logger.log( - `${dryRun ? 'Dry run - ' : ''}Deleted attachments ${attachmentIdsToSoftDelete.join(', ')}`, - ); - } - - private async cleanWorkspaceMembersAvatarUrl( - workspaceId: string, - dryRun: boolean, - ) { - this.logger.log( - `Cleaning workspace members avatarUrl for workspace ${workspaceId}`, - ); - const workspaceMemberRepository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace( - workspaceId, - 'workspaceMember', - ); - const workspaceMembers = await workspaceMemberRepository.find({ - where: { - avatarUrl: Not(Equal('')), - }, - }); - - const workspaceMemberIdsToUpdate: string[] = []; - - for (const workspaceMember of workspaceMembers) { - this.logger.log(`Processing workspaceMember ${workspaceMember.id}`); - - const isFileFound = await this.checkIfFileIsFound( - workspaceMember.avatarUrl, - workspaceId, - ); - - if (isFileFound) continue; - - workspaceMemberIdsToUpdate.push(workspaceMember.id); - } - - if (workspaceMemberIdsToUpdate.length === 0) return; - - if (!dryRun) - await workspaceMemberRepository.update(workspaceMemberIdsToUpdate, { - avatarUrl: '', - }); - - this.logger.log( - `${dryRun ? 'Dry run - ' : ''}Set avatarUrl to '' for workspaceMembers ${workspaceMemberIdsToUpdate.join(', ')}`, - ); - } - - private async cleanPeopleAvatarUrl(workspaceId: string, dryRun: boolean) { - this.logger.log(`Cleaning people avatarUrl for workspace ${workspaceId}`); - const personRepository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace( - workspaceId, - 'person', - { shouldBypassPermissionChecks: true }, - ); - const people = await personRepository.find({ - where: { - avatarUrl: Not(Equal('')), - }, - }); - - const personIdsToUpdate: string[] = []; - - for (const person of people) { - this.logger.log('Processing person', person.id); - - const isFileFound = await this.checkIfFileIsFound( - person.avatarUrl, - workspaceId, - ); - - if (!isFileFound) { - personIdsToUpdate.push(person.id); - } - } - - if (personIdsToUpdate.length === 0) return; - - if (!dryRun) - await personRepository.update(personIdsToUpdate, { - avatarUrl: '', - }); - - this.logger.log( - `${dryRun ? 'Dry run - ' : ''}Set avatarUrl to '' for people ${personIdsToUpdate.join(', ')}`, - ); - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/0-54/0-54-created-by-default-value.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/0-54/0-54-created-by-default-value.command.ts deleted file mode 100644 index b63daaaeae..0000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/0-54/0-54-created-by-default-value.command.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { InjectRepository } from '@nestjs/typeorm'; - -import { Command } from 'nest-commander'; -import { FieldMetadataType } from 'twenty-shared/types'; -import { Repository } from 'typeorm'; - -import { - ActiveOrSuspendedWorkspacesMigrationCommandRunner, - type RunOnWorkspaceArgs, -} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; -import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; -import { type ActorMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type'; -import { generateDefaultValue } from 'src/engine/metadata-modules/field-metadata/utils/generate-default-value'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { computeTableName } from 'src/engine/utils/compute-table-name.util'; -import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util'; - -@Command({ - name: 'upgrade:0-54:0-54-created-by-default-value', - description: 'Fix createdBy default value', -}) -export class FixCreatedByDefaultValueCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { - constructor( - @InjectRepository(WorkspaceEntity) - protected readonly workspaceRepository: Repository, - protected readonly twentyORMGlobalManager: TwentyORMGlobalManager, - @InjectRepository(ObjectMetadataEntity) - private readonly objectMetadataRepository: Repository, - ) { - super(workspaceRepository, twentyORMGlobalManager); - } - - override async runOnWorkspace({ - workspaceId, - dataSource, - }: RunOnWorkspaceArgs): Promise { - const objectsMetadataItems = await this.objectMetadataRepository.find({ - where: { workspaceId }, - relations: ['fields'], - }); - - for (const objectMetadataItem of objectsMetadataItems) { - const createdByFieldExists = objectMetadataItem.fields.some( - (field) => field.type === FieldMetadataType.ACTOR, - ); - - if (!createdByFieldExists) { - continue; - } - - const schemaName = getWorkspaceSchemaName(workspaceId); - - const tableName = computeTableName( - objectMetadataItem.nameSingular, - objectMetadataItem.isCustom, - ); - - const actualDefaultValue = ( - await dataSource.query( - ` - SELECT column_default FROM information_schema.columns - WHERE table_schema = '${schemaName}' - AND table_name = '${tableName}' - AND column_name = 'createdBySource'; - `, - undefined, // parameters - undefined, // queryRunner - { - shouldBypassPermissionChecks: true, - }, - ) - )?.[0]?.column_default; - - if (actualDefaultValue !== null) { - continue; - } - - const createdByDefaultValues = generateDefaultValue( - FieldMetadataType.ACTOR, - ) as ActorMetadata; - - await dataSource.query( - ` - ALTER TABLE "${schemaName}"."${tableName}" - ALTER COLUMN "createdBySource" SET DEFAULT ${createdByDefaultValues.source}, - ALTER COLUMN "createdByName" SET DEFAULT ${createdByDefaultValues.name}, - ALTER COLUMN "createdByContext" SET DEFAULT '${JSON.stringify(createdByDefaultValues.context)}'; - `, - undefined, // parameters - undefined, // queryRunner - { - shouldBypassPermissionChecks: true, - }, - ); - } - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/0-54/0-54-fix-standard-select-fields-position.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/0-54/0-54-fix-standard-select-fields-position.command.ts deleted file mode 100644 index e745965656..0000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/0-54/0-54-fix-standard-select-fields-position.command.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { InjectRepository } from '@nestjs/typeorm'; - -import { Command } from 'nest-commander'; -import { Repository } from 'typeorm'; - -import { - ActiveOrSuspendedWorkspacesMigrationCommandRunner, - type RunOnWorkspaceArgs, -} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; -import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; -import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-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 { TASK_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; - -@Command({ - name: 'upgrade:0-54:fix-standard-select-fields-position', - description: 'Fix standard select fields position', -}) -export class FixStandardSelectFieldsPositionCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { - constructor( - @InjectRepository(WorkspaceEntity) - protected readonly workspaceRepository: Repository, - protected readonly twentyORMGlobalManager: TwentyORMGlobalManager, - @InjectRepository(FieldMetadataEntity) - private readonly fieldMetadataRepository: Repository, - private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService, - ) { - super(workspaceRepository, twentyORMGlobalManager); - } - - override async runOnWorkspace({ - index, - total, - workspaceId, - options, - }: RunOnWorkspaceArgs): Promise { - this.logger.log( - `Running command for workspace ${workspaceId} ${index + 1}/${total}`, - ); - - await this.overrideTaskStatusFieldMetadataPosition({ - workspaceId, - dryRun: options.dryRun, - }); - } - - private async overrideTaskStatusFieldMetadataPosition({ - workspaceId, - dryRun, - }: { - workspaceId: string; - dryRun: boolean | undefined; - }) { - const taskStatusFieldMetadata = await this.fieldMetadataRepository.findOne({ - where: { - workspaceId, - standardId: TASK_STANDARD_FIELD_IDS.status, - }, - }); - - if (!taskStatusFieldMetadata) { - this.logger.warn( - `Task status field metadata not found for workspace ${workspaceId}. Exiting.`, - ); - - return; - } - - const scannedPositions = new Set(); - let biggestPosition = -1; - - // Sort options by position for consistent processing - const sortedOptions = (taskStatusFieldMetadata.options ?? []).sort( - (a, b) => a.position - b.position, - ); - - for (const option of sortedOptions) { - if (scannedPositions.has(option.position)) { - this.logger.warn( - `Found duplicate position ${option.position} for option ${option.value} in task status field metadata for workspace ${workspaceId}.`, - ); - - option.position = biggestPosition + 1; - } - - biggestPosition = Math.max(biggestPosition, option.position); - scannedPositions.add(option.position); - } - - if (!dryRun) { - await this.fieldMetadataRepository.update( - { - workspaceId, - standardId: TASK_STANDARD_FIELD_IDS.status, - }, - { - options: sortedOptions, - }, - ); - - await this.workspaceMetadataVersionService.incrementMetadataVersion( - workspaceId, - ); - } - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/0-54/0-54-lowercase-user-and-invitation-emails.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/0-54/0-54-lowercase-user-and-invitation-emails.command.ts deleted file mode 100644 index 901fb3031f..0000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/0-54/0-54-lowercase-user-and-invitation-emails.command.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { InjectRepository } from '@nestjs/typeorm'; - -import { Command } from 'nest-commander'; -import { Raw, Repository } from 'typeorm'; - -import { - ActiveOrSuspendedWorkspacesMigrationCommandRunner, - type RunOnWorkspaceArgs, -} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; -import { - AppTokenEntity, - AppTokenType, -} from 'src/engine/core-modules/app-token/app-token.entity'; -import { UserEntity } from 'src/engine/core-modules/user/user.entity'; -import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; -import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; - -@Command({ - name: 'upgrade:0-54:lowercase-user-and-invitation-emails', - description: 'Lowercase user and invitation emails', -}) -export class LowercaseUserAndInvitationEmailsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { - constructor( - @InjectRepository(UserEntity) - protected readonly userRepository: Repository, - @InjectRepository(AppTokenEntity) - protected readonly appTokenRepository: Repository, - @InjectRepository(WorkspaceEntity) - protected readonly workspaceRepository: Repository, - protected readonly twentyORMGlobalManager: TwentyORMGlobalManager, - ) { - super(workspaceRepository, twentyORMGlobalManager); - } - - override async runOnWorkspace({ - index, - total, - workspaceId, - options, - }: RunOnWorkspaceArgs): Promise { - this.logger.log( - `Running command for workspace ${workspaceId} ${index + 1}/${total}`, - ); - - await this.lowercaseUserEmails(workspaceId, !!options.dryRun); - await this.lowercaseInvitationEmails(workspaceId, !!options.dryRun); - } - - private async lowercaseUserEmails(workspaceId: string, dryRun: boolean) { - const users = await this.userRepository.find({ - where: { - userWorkspaces: { - workspaceId, - }, - email: Raw((alias) => `LOWER(${alias}) != ${alias}`), - }, - }); - - if (users.length === 0) return; - - for (const user of users) { - if (!dryRun) { - await this.userRepository.update( - { - id: user.id, - }, - { - email: user.email.toLowerCase(), - }, - ); - } - - this.logger.log( - `Lowercased user email ${user.email} for workspace ${workspaceId}`, - ); - } - } - - private async lowercaseInvitationEmails( - workspaceId: string, - dryRun: boolean, - ) { - const appTokens = await this.appTokenRepository.find({ - where: { - workspaceId, - type: AppTokenType.InvitationToken, - context: Raw((_) => `LOWER(context->>'email') != context->>'email'`), - }, - }); - - if (appTokens.length === 0) return; - - for (const appToken of appTokens) { - if (!dryRun) { - await this.appTokenRepository.update( - { - id: appToken.id, - }, - { - context: { - ...appToken.context, - email: appToken.context?.email.toLowerCase(), - }, - }, - ); - } - - this.logger.log( - `Lowercased invitation email ${appToken.context?.email} for workspace ${workspaceId}`, - ); - } - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/0-54/0-54-migrate-default-avatar-url-to-user-workspace.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/0-54/0-54-migrate-default-avatar-url-to-user-workspace.command.ts deleted file mode 100644 index 238566ccf9..0000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/0-54/0-54-migrate-default-avatar-url-to-user-workspace.command.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { InjectRepository } from '@nestjs/typeorm'; - -import { isNonEmptyString } from '@sniptt/guards'; -import { Command } from 'nest-commander'; -import { Repository } from 'typeorm'; - -import { - ActiveOrSuspendedWorkspacesMigrationCommandRunner, - type RunOnWorkspaceArgs, -} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; -import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; -import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; -import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; - -@Command({ - name: 'upgrade:0-54:migrate-default-avatar-url-to-user-workspace', - description: 'Migrate default avatar url to user workspace', -}) -export class MigrateDefaultAvatarUrlToUserWorkspaceCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { - constructor( - @InjectRepository(WorkspaceEntity) - protected readonly workspaceRepository: Repository, - @InjectRepository(UserWorkspaceEntity) - protected readonly userWorkspaceRepository: Repository, - protected readonly twentyORMGlobalManager: TwentyORMGlobalManager, - ) { - super(workspaceRepository, twentyORMGlobalManager); - } - - override async runOnWorkspace({ - index, - total, - workspaceId, - options, - }: RunOnWorkspaceArgs): Promise { - this.logger.log( - `Running command for workspace ${workspaceId} ${index + 1}/${total}`, - ); - - await this.migrateDefaultAvatarUrlToUserWorkspace({ - workspaceId, - dryRun: !!options.dryRun, - }); - } - - private async migrateDefaultAvatarUrlToUserWorkspace({ - workspaceId, - dryRun, - }: { - workspaceId: string; - dryRun: boolean; - }) { - const workspace = await this.workspaceRepository.findOneOrFail({ - where: { - id: workspaceId, - }, - relations: ['workspaceUsers', 'workspaceUsers.user'], - }); - - for (const workspaceUser of workspace.workspaceUsers) { - if (isNonEmptyString(workspaceUser.user.defaultAvatarUrl)) { - const userWorkspacesCount = await this.userWorkspaceRepository.count({ - where: { - userId: workspaceUser.user.id, - }, - }); - - if (userWorkspacesCount === 1) { - if (!dryRun) - await this.userWorkspaceRepository.update( - { - userId: workspaceUser.user.id, - workspaceId: workspace.id, - }, - { - defaultAvatarUrl: workspaceUser.user.defaultAvatarUrl, - }, - ); - - this.logger.log( - `Updated default avatar url for user ${workspaceUser.user.id} on user workspace ${workspaceUser.id}`, - ); - } - } - } - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/0-54/0-54-upgrade-version-command.module.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/0-54/0-54-upgrade-version-command.module.ts deleted file mode 100644 index ae5562f4fc..0000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/0-54/0-54-upgrade-version-command.module.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; - -import { CleanNotFoundFilesCommand } from 'src/database/commands/upgrade-version-command/0-54/0-54-clean-not-found-files.command'; -import { FixCreatedByDefaultValueCommand } from 'src/database/commands/upgrade-version-command/0-54/0-54-created-by-default-value.command'; -import { FixStandardSelectFieldsPositionCommand } from 'src/database/commands/upgrade-version-command/0-54/0-54-fix-standard-select-fields-position.command'; -import { LowercaseUserAndInvitationEmailsCommand } from 'src/database/commands/upgrade-version-command/0-54/0-54-lowercase-user-and-invitation-emails.command'; -import { MigrateDefaultAvatarUrlToUserWorkspaceCommand } from 'src/database/commands/upgrade-version-command/0-54/0-54-migrate-default-avatar-url-to-user-workspace.command'; -import { AppTokenEntity } from 'src/engine/core-modules/app-token/app-token.entity'; -import { FileStorageModule } from 'src/engine/core-modules/file-storage/file-storage.module'; -import { FileModule } from 'src/engine/core-modules/file/file.module'; -import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; -import { UserEntity } from 'src/engine/core-modules/user/user.entity'; -import { WorkspaceEntity } 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 { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module'; -import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.module'; - -@Module({ - imports: [ - TypeOrmModule.forFeature([ - WorkspaceEntity, - AppTokenEntity, - UserEntity, - UserWorkspaceEntity, - ]), - TypeOrmModule.forFeature([FieldMetadataEntity, ObjectMetadataEntity]), - WorkspaceDataSourceModule, - WorkspaceMigrationRunnerModule, - WorkspaceMetadataVersionModule, - FileModule, - FileStorageModule, - ], - providers: [ - FixStandardSelectFieldsPositionCommand, - FixCreatedByDefaultValueCommand, - CleanNotFoundFilesCommand, - LowercaseUserAndInvitationEmailsCommand, - MigrateDefaultAvatarUrlToUserWorkspaceCommand, - ], - exports: [ - FixStandardSelectFieldsPositionCommand, - FixCreatedByDefaultValueCommand, - CleanNotFoundFilesCommand, - LowercaseUserAndInvitationEmailsCommand, - MigrateDefaultAvatarUrlToUserWorkspaceCommand, - ], -}) -export class V0_54_UpgradeVersionCommandModule {} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/0-55/0-55-deduplicate-indexed-fields.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/0-55/0-55-deduplicate-indexed-fields.command.ts deleted file mode 100644 index b225bf6366..0000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/0-55/0-55-deduplicate-indexed-fields.command.ts +++ /dev/null @@ -1,298 +0,0 @@ -import { InjectRepository } from '@nestjs/typeorm'; - -import { Command } from 'nest-commander'; -import { IsNull, Repository } from 'typeorm'; - -import { - ActiveOrSuspendedWorkspacesMigrationCommandRunner, - type RunOnWorkspaceArgs, -} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; -import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; -import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; - -@Command({ - name: 'upgrade:0-55:deduplicate-indexed-fields', - description: 'Deduplicate fields where we want to setup the index back on', -}) -export class DeduplicateIndexedFieldsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { - constructor( - @InjectRepository(WorkspaceEntity) - protected readonly workspaceRepository: Repository, - protected readonly twentyORMGlobalManager: TwentyORMGlobalManager, - ) { - super(workspaceRepository, twentyORMGlobalManager); - } - - override async runOnWorkspace({ - workspaceId, - options, - }: RunOnWorkspaceArgs): Promise { - this.logger.log( - `Deduplicating indexed fields for workspace ${workspaceId}`, - ); - - // searchVector should not be problematic since you cannot duplicate a search vector from what I guess - // in company, domainName is indexed with decorator @WorkspaceIsUnique() - // in person, email is indexed with decorator @WorkspaceIsUnique() - // in message-channel-message-association, the object is indexed with decorator @WorkspaceIndex(['messageChannelId', 'messageId'] - // in view-field, the object is indexed with decorator @WorkspaceIndex(['fieldMetadataId', 'viewId'] - // in view-sort, the object is indexed with decorator @WorkspaceIndex(['viewId', 'fieldMetadataId'] - - // not needed since no unique constraint on this one: - // in oportunity, stage is indexed with decorator @WorkspaceFieldIndex() - - await this.enforceUniqueConstraintsForWorkspace( - workspaceId, - options.dryRun ?? false, - ); - } - - private async enforceUniqueConstraintsForWorkspace( - workspaceId: string, - dryRun: boolean, - ): Promise { - await this.enforceUniqueCompanyDomainName(workspaceId, dryRun); - - await this.enforceUniquePersonEmail(workspaceId, dryRun); - - await this.enforceUniqueMessageChannelMessageAssociation( - workspaceId, - dryRun, - ); - - await this.enforceUniqueViewField(workspaceId, dryRun); - - await this.enforceUniqueViewSort(workspaceId, dryRun); - } - - private async enforceUniqueCompanyDomainName( - workspaceId: string, - dryRun: boolean, - ): Promise { - const companyRepository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace( - workspaceId, - 'company', - { shouldBypassPermissionChecks: true }, - ); - - const duplicates = await companyRepository - .createQueryBuilder('company') - .select('company.domainNamePrimaryLinkUrl') - .addSelect('COUNT(*)', 'count') - .where('company.deletedAt IS NULL') - .andWhere('company.domainNamePrimaryLinkUrl IS NOT NULL') - .andWhere("company.domainNamePrimaryLinkUrl != ''") - .groupBy('company.domainNamePrimaryLinkUrl') - .having('COUNT(*) > 1') - .getRawMany(); - - for (const duplicate of duplicates) { - const { company_domainNamePrimaryLinkUrl } = duplicate; - const companies = await companyRepository.find({ - where: { - domainName: { - primaryLinkUrl: company_domainNamePrimaryLinkUrl, - }, - deletedAt: IsNull(), - }, - order: { createdAt: 'DESC' }, - }); - - for (let i = 1; i < companies.length; i++) { - const newdomainNamePrimaryLinkUrl = `${company_domainNamePrimaryLinkUrl}${i}`; - - if (!dryRun) { - await companyRepository.update(companies[i].id, { - domainNamePrimaryLinkUrl: newdomainNamePrimaryLinkUrl, - }); - } - this.logger.log( - `Updated company ${companies[i].id} domainName from ${company_domainNamePrimaryLinkUrl} to ${newdomainNamePrimaryLinkUrl}`, - ); - } - } - } - - private async enforceUniquePersonEmail( - workspaceId: string, - dryRun: boolean, - ): Promise { - const personRepository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace( - workspaceId, - 'person', - { shouldBypassPermissionChecks: true }, - ); - - const duplicates = await personRepository - .createQueryBuilder('person') - .select('person.emailsPrimaryEmail') - .addSelect('COUNT(*)', 'count') - .where('person.deletedAt IS NULL') - .andWhere('person.emailsPrimaryEmail IS NOT NULL') - .andWhere("person.emailsPrimaryEmail != ''") - .groupBy('person.emailsPrimaryEmail') - .having('COUNT(*) > 1') - .getRawMany(); - - for (const duplicate of duplicates) { - const { person_emailsPrimaryEmail } = duplicate; - const persons = await personRepository.find({ - where: { - emails: { - primaryEmail: person_emailsPrimaryEmail, - }, - deletedAt: IsNull(), - }, - order: { createdAt: 'DESC' }, - }); - - for (let i = 1; i < persons.length; i++) { - const newEmail = person_emailsPrimaryEmail?.includes('@') - ? `${person_emailsPrimaryEmail.split('@')[0]}+${i}@${person_emailsPrimaryEmail.split('@')[1]}` - : `${person_emailsPrimaryEmail}+${i}`; - - if (!dryRun) { - await personRepository.update(persons[i].id, { - emailsPrimaryEmail: newEmail, - }); - } - this.logger.log( - `Updated person ${persons[i].id} emailsPrimaryEmail from ${person_emailsPrimaryEmail} to ${newEmail}`, - ); - } - } - } - - private async enforceUniqueMessageChannelMessageAssociation( - workspaceId: string, - dryRun: boolean, - ): Promise { - const messageChannelMessageAssociationRepository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace( - workspaceId, - 'messageChannelMessageAssociation', - ); - - const duplicates = await messageChannelMessageAssociationRepository - .createQueryBuilder('messageChannelMessageAssociation') - .select('messageChannelMessageAssociation.messageId') - .addSelect('messageChannelMessageAssociation.messageChannelId') - .addSelect('COUNT(*)', 'count') - .where('messageChannelMessageAssociation.deletedAt IS NULL') - .groupBy('messageChannelMessageAssociation.messageId') - .addGroupBy('messageChannelMessageAssociation.messageChannelId') - .having('COUNT(*) > 1') - .getRawMany(); - - for (const duplicate of duplicates) { - const { - messageChannelMessageAssociation_messageId, - messageChannelMessageAssociation_messageChannelId, - } = duplicate; - const messageChannelMessageAssociations = - await messageChannelMessageAssociationRepository.find({ - where: { - messageId: messageChannelMessageAssociation_messageId, - messageChannelId: messageChannelMessageAssociation_messageChannelId, - deletedAt: IsNull(), - }, - order: { createdAt: 'DESC' }, - }); - - for (let i = 1; i < messageChannelMessageAssociations.length; i++) { - if (!dryRun) { - await messageChannelMessageAssociationRepository.delete( - messageChannelMessageAssociations[i].id, - ); - } - this.logger.log( - `Deleted messageChannelMessageAssociation ${messageChannelMessageAssociations[i].id}`, - ); - } - } - } - - private async enforceUniqueViewField( - workspaceId: string, - dryRun: boolean, - ): Promise { - const viewFieldRepository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace( - workspaceId, - 'viewField', - ); - - const duplicates = await viewFieldRepository - .createQueryBuilder('viewField') - .select('viewField.fieldMetadataId') - .addSelect('viewField.viewId') - .addSelect('COUNT(*)', 'count') - .where('viewField.deletedAt IS NULL') - .groupBy('viewField.fieldMetadataId') - .addGroupBy('viewField.viewId') - .having('COUNT(*) > 1') - .getRawMany(); - - for (const duplicate of duplicates) { - const { viewField_fieldMetadataId, viewField_viewId } = duplicate; - const viewFields = await viewFieldRepository.find({ - where: { - fieldMetadataId: viewField_fieldMetadataId, - viewId: viewField_viewId, - deletedAt: IsNull(), - }, - order: { createdAt: 'DESC' }, - }); - - for (let i = 1; i < viewFields.length; i++) { - if (!dryRun) { - await viewFieldRepository.delete(viewFields[i].id); - } - this.logger.log(`Deleted viewField ${viewFields[i].id}`); - } - } - } - - private async enforceUniqueViewSort( - workspaceId: string, - dryRun: boolean, - ): Promise { - const viewSortRepository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace( - workspaceId, - 'viewSort', - ); - - const duplicates = await viewSortRepository - .createQueryBuilder('viewSort') - .select('viewSort.viewId') - .addSelect('viewSort.fieldMetadataId') - .addSelect('COUNT(*)', 'count') - .where('viewSort.deletedAt IS NULL') - .groupBy('viewSort.viewId') - .addGroupBy('viewSort.fieldMetadataId') - .having('COUNT(*) > 1') - .getRawMany(); - - for (const duplicate of duplicates) { - const { viewSort_viewId, viewSort_fieldMetadataId } = duplicate; - const viewSorts = await viewSortRepository.find({ - where: { - fieldMetadataId: viewSort_fieldMetadataId, - viewId: viewSort_viewId, - deletedAt: IsNull(), - }, - order: { createdAt: 'DESC' }, - }); - - for (let i = 1; i < viewSorts.length; i++) { - if (!dryRun) { - await viewSortRepository.delete(viewSorts[i].id); - } - this.logger.log(`Deleted viewSort ${viewSorts[i].id}`); - } - } - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/0-55/0-55-upgrade-version-command.module.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/0-55/0-55-upgrade-version-command.module.ts deleted file mode 100644 index b4a3c3f89b..0000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/0-55/0-55-upgrade-version-command.module.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; - -import { DeduplicateIndexedFieldsCommand } from 'src/database/commands/upgrade-version-command/0-55/0-55-deduplicate-indexed-fields.command'; -import { AppTokenEntity } from 'src/engine/core-modules/app-token/app-token.entity'; -import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; -import { UserEntity } from 'src/engine/core-modules/user/user.entity'; -import { WorkspaceEntity } 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 { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module'; -import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.module'; - -@Module({ - imports: [ - TypeOrmModule.forFeature([ - WorkspaceEntity, - AppTokenEntity, - UserEntity, - UserWorkspaceEntity, - FieldMetadataEntity, - ObjectMetadataEntity, - ]), - WorkspaceDataSourceModule, - WorkspaceMigrationRunnerModule, - WorkspaceMetadataVersionModule, - ], - providers: [DeduplicateIndexedFieldsCommand], - exports: [DeduplicateIndexedFieldsCommand], -}) -export class V0_55_UpgradeVersionCommandModule {} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-1/1-1-add-enqueued-status-to-workflow-run.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-1/1-1-add-enqueued-status-to-workflow-run.command.ts deleted file mode 100644 index eb72911e10..0000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-1/1-1-add-enqueued-status-to-workflow-run.command.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; - -import { Command } from 'nest-commander'; -import { DataSource, Repository } from 'typeorm'; - -import { - ActiveOrSuspendedWorkspacesMigrationCommandRunner, - type RunOnWorkspaceArgs, -} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; -import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; -import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util'; -import { WORKFLOW_RUN_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; -import { WorkflowRunStatus } from 'src/modules/workflow/common/standard-objects/workflow-run.workspace-entity'; - -@Command({ - name: 'upgrade:1-1:add-enqueued-status-to-workflow-run', - description: 'Add enqueued status to workflow run', -}) -export class AddEnqueuedStatusToWorkflowRunCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { - constructor( - @InjectRepository(WorkspaceEntity) - protected readonly workspaceRepository: Repository, - protected readonly twentyORMGlobalManager: TwentyORMGlobalManager, - @InjectRepository(FieldMetadataEntity) - private readonly fieldMetadataRepository: Repository, - @InjectDataSource() - private readonly coreDataSource: DataSource, - ) { - super(workspaceRepository, twentyORMGlobalManager); - } - - override async runOnWorkspace({ - workspaceId, - options, - }: RunOnWorkspaceArgs): Promise { - this.logger.log( - `Adding enqueued status to workflow run for workspace ${workspaceId}`, - ); - - const workflowRunStatusFieldMetadata = - await this.fieldMetadataRepository.findOne({ - where: { - standardId: WORKFLOW_RUN_STANDARD_FIELD_IDS.status, - }, - }); - - if (!workflowRunStatusFieldMetadata) { - this.logger.error( - `Workflow run status field metadata not found for workspace ${workspaceId}`, - ); - - return; - } - - const workflowRunStatusFieldMetadataOptions = - workflowRunStatusFieldMetadata.options; - - // check if enqueued status is already in the field metadata options - if ( - workflowRunStatusFieldMetadataOptions?.some( - (option) => option.value === WorkflowRunStatus.ENQUEUED, - ) - ) { - this.logger.log( - `Workflow run status field metadata options already contain enqueued status for workspace ${workspaceId}`, - ); - - return; - } else if (options.dryRun) { - this.logger.log( - `Would add enqueued status to workflow run status field metadata for workspace ${workspaceId}`, - ); - } else { - workflowRunStatusFieldMetadataOptions?.push({ - value: WorkflowRunStatus.ENQUEUED, - label: 'Enqueued', - position: 4, - color: 'blue', - }); - - await this.fieldMetadataRepository.save(workflowRunStatusFieldMetadata); - - this.logger.log( - `Enqueued status added to workflow run status field metadata for workspace ${workspaceId}`, - ); - } - - const schemaName = getWorkspaceSchemaName(workspaceId); - - if (options.dryRun) { - this.logger.log( - `Would try to add enqueued status to workflow run status enum for workspace ${workspaceId}`, - ); - } else { - try { - await this.coreDataSource.query( - `ALTER TYPE ${schemaName}."workflowRun_status_enum" ADD VALUE 'ENQUEUED'`, - ); - this.logger.log( - `Enqueued status added to workflow run status enum for workspace ${workspaceId}`, - ); - } catch (error) { - this.logger.error( - `Error adding enqueued status to workflow run status enum for workspace ${workspaceId}: ${error}`, - ); - } - } - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-1/1-1-fix-schema-array-type.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-1/1-1-fix-schema-array-type.command.ts deleted file mode 100644 index a78e704b3c..0000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-1/1-1-fix-schema-array-type.command.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; - -import { Command } from 'nest-commander'; -import { FieldMetadataType } from 'twenty-shared/types'; -import { DataSource, Repository } from 'typeorm'; - -import { - ActiveOrSuspendedWorkspacesMigrationCommandRunner, - type RunOnWorkspaceArgs, -} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; -import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; -import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util'; -import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; -import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util'; -import { DatabaseStructureService } from 'src/engine/workspace-manager/workspace-health/services/database-structure.service'; - -@Command({ - name: 'upgrade:1-1:fix-schema-array-type', - description: 'Fix columns for ARRAY fields to be text[] in the DB schema', -}) -export class FixSchemaArrayTypeCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { - constructor( - @InjectRepository(WorkspaceEntity) - protected readonly workspaceRepository: Repository, - protected readonly twentyORMGlobalManager: TwentyORMGlobalManager, - private readonly databaseStructureService: DatabaseStructureService, - @InjectDataSource() - private readonly coreDataSource: DataSource, - @InjectRepository(FieldMetadataEntity) - private readonly fieldMetadataRepository: Repository, - ) { - super(workspaceRepository, twentyORMGlobalManager); - } - - override async runOnWorkspace({ - workspaceId, - options, - }: RunOnWorkspaceArgs): Promise { - this.logger.log(`Fixing ARRAY field columns for workspace ${workspaceId}`); - - const arrayFields: FieldMetadataEntity[] = - await this.fieldMetadataRepository.find({ - where: { - workspaceId, - type: FieldMetadataType.ARRAY, - isCustom: true, - }, - relations: ['object'], - }); - - if (arrayFields.length === 0) { - this.logger.log('No ARRAY fields found for this workspace.'); - - return; - } - - for (const field of arrayFields) { - const object = field.object; - - const tableName = computeObjectTargetTable(object); - const schemaName = getWorkspaceSchemaName(workspaceId); - const columns = - await this.databaseStructureService.getWorkspaceTableColumns( - schemaName, - tableName, - ); - const columnName = computeColumnName(field); - const dbColumn = columns.find((col) => col.columnName === columnName); - - if (!dbColumn) { - this.logger.warn( - `Column ${columnName} not found in table ${schemaName}.${tableName}`, - ); - continue; - } - if (dbColumn.dataType === 'text[]' && dbColumn.isArray) { - continue; - } - this.logger.log( - `Altering column ${schemaName}.${tableName}.${columnName} to type text[] (was ${dbColumn.dataType})`, - ); - if (!options.dryRun) { - const queryRunner = this.coreDataSource.createQueryRunner(); - - await queryRunner.connect(); - try { - await queryRunner.query( - `ALTER TABLE "${schemaName}"."${tableName}" ALTER COLUMN "${columnName}" TYPE text[] USING "${columnName}"::text[];`, - ); - } finally { - await queryRunner.release(); - } - } - } - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-1/1-1-fix-update-standard-field-is-label-synced-with-name.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-1/1-1-fix-update-standard-field-is-label-synced-with-name.command.ts deleted file mode 100644 index f654d4c5a3..0000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-1/1-1-fix-update-standard-field-is-label-synced-with-name.command.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { InjectRepository } from '@nestjs/typeorm'; - -import { Command } from 'nest-commander'; -import { IsNull, Not, Repository } from 'typeorm'; - -import { - ActiveOrSuspendedWorkspacesMigrationCommandRunner, - type RunOnWorkspaceArgs, -} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; -import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; -import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { computeMetadataNameFromLabel } from 'src/engine/metadata-modules/utils/validate-name-and-label-are-sync-or-throw.util'; -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'; - -@Command({ - name: 'upgrade:1-1:fix-update-standard-fields-is-label-synced-with-name', - description: - 'Fix isLabelSyncedWithName property for standard fields to match actual label-name synchronization state', -}) -export class FixUpdateStandardFieldsIsLabelSyncedWithName extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { - constructor( - @InjectRepository(WorkspaceEntity) - protected readonly workspaceRepository: Repository, - protected readonly twentyORMGlobalManager: TwentyORMGlobalManager, - @InjectRepository(FieldMetadataEntity) - private readonly fieldMetadataRepository: Repository, - private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService, - ) { - super(workspaceRepository, twentyORMGlobalManager); - } - - override async runOnWorkspace({ - workspaceId, - options, - }: RunOnWorkspaceArgs): Promise { - this.logger.log(`Updating standard fields for workspace ${workspaceId}`); - const workspaceStandardFields = await this.fieldMetadataRepository.find({ - where: { - workspaceId, - isCustom: false, - standardId: Not(IsNull()), - }, - }); - - let updatedFields = 0; - - for (const field of workspaceStandardFields) { - const isLabelSyncedWithName = - computeMetadataNameFromLabel(field.label) === field.name; - - if (field.isLabelSyncedWithName === isLabelSyncedWithName) { - continue; - } - - if (!options.dryRun) { - await this.fieldMetadataRepository.update(field.id, { - isLabelSyncedWithName, - }); - } - updatedFields++; - this.logger.log(`Updated isLabelSyncedMetadata for field ${field.id}`); - } - - if (!options.dryRun && updatedFields > 0) { - await this.workspaceMetadataVersionService.incrementMetadataVersion( - workspaceId, - ); - } - this.logger.log( - `Updated ${updatedFields} field.s for workspace ${workspaceId}`, - ); - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-1/1-1-migrate-workflow-run-state.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-1/1-1-migrate-workflow-run-state.command.ts deleted file mode 100644 index 056b629a58..0000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-1/1-1-migrate-workflow-run-state.command.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { InjectRepository } from '@nestjs/typeorm'; - -import { MoreThan, Repository } from 'typeorm'; -import { Command, Option } from 'nest-commander'; -import { isDefined } from 'twenty-shared/utils'; -import { StepStatus, type WorkflowRunStepInfos } from 'twenty-shared/workflow'; - -import { - ActiveOrSuspendedWorkspacesMigrationCommandRunner, - type RunOnWorkspaceArgs, -} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; -import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; -import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { - type WorkflowRunState, - type WorkflowRunOutput, - type WorkflowRunWorkspaceEntity, -} from 'src/modules/workflow/common/standard-objects/workflow-run.workspace-entity'; - -const DEFAULT_CHUNK_SIZE = 500; - -@Command({ - name: 'upgrade:1-1:migrate-workflow-run-state', - description: 'Migrate state column in workflow run records', -}) -export class MigrateWorkflowRunStatesCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { - private afterDate: string | undefined; - private chunkSize = DEFAULT_CHUNK_SIZE; - - constructor( - @InjectRepository(WorkspaceEntity) - protected readonly workspaceRepository: Repository, - protected readonly twentyORMGlobalManager: TwentyORMGlobalManager, - ) { - super(workspaceRepository, twentyORMGlobalManager); - } - - @Option({ - flags: '--after-date [after_date]', - description: 'Only select records after this date (YYYY-MM-DD).', - required: false, - }) - parseAfterDate(val: string): string | undefined { - const date = new Date(val); - - if (isNaN(date.getTime())) { - throw new Error(`Invalid date format: ${val}`); - } - - const afterDate = date.toISOString(); - - this.afterDate = afterDate; - - return afterDate; - } - - @Option({ - flags: '--chunk-size [chunk_size]', - description: - 'Split workflowRuns into chunks for each workspaces (default 500)', - required: false, - }) - parseChunkSize(val: number): number { - if (isNaN(val) || val <= 0) { - throw new Error(`Invalid chunk size: ${val}. Should be greater than 0`); - } - - this.chunkSize = val; - - return this.chunkSize; - } - - override async runOnWorkspace({ - workspaceId, - }: RunOnWorkspaceArgs): Promise { - const workflowRunRepository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace( - workspaceId, - 'workflowRun', - { shouldBypassPermissionChecks: true }, - ); - - const workflowRunCount = await workflowRunRepository.count(); - - const chunkCount = Math.ceil(workflowRunCount / this.chunkSize); - - this.logger.log( - `Migrate ${workflowRunCount} workflowRun state in ${chunkCount} chunks of size ${this.chunkSize}`, - ); - - for (let offset = 0; offset < chunkCount; offset += 1) { - this.logger.log(`- Proceeding chunk ${offset + 1}/${chunkCount}`); - - const findOption = isDefined(this.afterDate) - ? { where: { startedAt: MoreThan(this.afterDate) } } - : {}; - - const workflowRuns = (await workflowRunRepository.find({ - ...findOption, - skip: offset * this.chunkSize, - take: this.chunkSize, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - })) as any[]; // We type as any as workflowRun output has been removed since 1.1.0 release - - for (const workflowRun of workflowRuns) { - const output = workflowRun.output; - - if (!isDefined(output)) { - continue; - } - - const state = this.buildRunStateFromOutput(output); - - await workflowRunRepository.update(workflowRun.id, { - state, - }); - } - } - } - - private buildRunStateFromOutput(output: WorkflowRunOutput): WorkflowRunState { - const stepInfos: WorkflowRunStepInfos = Object.fromEntries( - output.flow.steps.map((step) => { - const stepOutput = output.stepsOutput?.[step.id]; - const status = stepOutput?.pendingEvent - ? StepStatus.PENDING - : stepOutput?.error - ? StepStatus.FAILED - : stepOutput?.result - ? StepStatus.SUCCESS - : StepStatus.NOT_STARTED; - - return [ - step.id, - { - result: stepOutput?.result, - error: stepOutput?.error, - status, - }, - ]; - }), - ); - - stepInfos['trigger'] = { - result: output?.stepsOutput?.trigger?.result, - status: StepStatus.SUCCESS, - }; - - return { - flow: output?.flow, - workflowRunError: output?.error, - stepInfos, - }; - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-1/1-1-upgrade-version-command.module.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-1/1-1-upgrade-version-command.module.ts deleted file mode 100644 index 20f289893f..0000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-1/1-1-upgrade-version-command.module.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; - -import { AddEnqueuedStatusToWorkflowRunCommand } from 'src/database/commands/upgrade-version-command/1-1/1-1-add-enqueued-status-to-workflow-run.command'; -import { FixSchemaArrayTypeCommand } from 'src/database/commands/upgrade-version-command/1-1/1-1-fix-schema-array-type.command'; -import { FixUpdateStandardFieldsIsLabelSyncedWithName } from 'src/database/commands/upgrade-version-command/1-1/1-1-fix-update-standard-field-is-label-synced-with-name.command'; -import { MigrateWorkflowRunStatesCommand } from 'src/database/commands/upgrade-version-command/1-1/1-1-migrate-workflow-run-state.command'; -import { TypeORMModule } from 'src/database/typeorm/typeorm.module'; -import { ApiKeyEntity } from 'src/engine/core-modules/api-key/api-key.entity'; -import { ApiKeyModule } from 'src/engine/core-modules/api-key/api-key.module'; -import { AppTokenEntity } from 'src/engine/core-modules/app-token/app-token.entity'; -import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; -import { UserEntity } from 'src/engine/core-modules/user/user.entity'; -import { WebhookEntity } from 'src/engine/core-modules/webhook/webhook.entity'; -import { WebhookModule } from 'src/engine/core-modules/webhook/webhook.module'; -import { WorkspaceEntity } 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 { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module'; -import { WorkspaceHealthModule } from 'src/engine/workspace-manager/workspace-health/workspace-health.module'; -import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.module'; - -@Module({ - imports: [ - TypeOrmModule.forFeature([ - WorkspaceEntity, - AppTokenEntity, - UserEntity, - UserWorkspaceEntity, - FieldMetadataEntity, - ObjectMetadataEntity, - ApiKeyEntity, - WebhookEntity, - ]), - WorkspaceDataSourceModule, - WorkspaceMigrationRunnerModule, - WorkspaceMetadataVersionModule, - WorkspaceHealthModule, - TypeORMModule, - ApiKeyModule, - WebhookModule, - ], - providers: [ - FixUpdateStandardFieldsIsLabelSyncedWithName, - FixSchemaArrayTypeCommand, - MigrateWorkflowRunStatesCommand, - AddEnqueuedStatusToWorkflowRunCommand, - ], - exports: [ - FixUpdateStandardFieldsIsLabelSyncedWithName, - FixSchemaArrayTypeCommand, - MigrateWorkflowRunStatesCommand, - AddEnqueuedStatusToWorkflowRunCommand, - ], -}) -export class V1_1_UpgradeVersionCommandModule {} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-2/1-2-add-enqueued-status-to-workflow-run-v2.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-2/1-2-add-enqueued-status-to-workflow-run-v2.command.ts deleted file mode 100644 index 3ce5ae4214..0000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-2/1-2-add-enqueued-status-to-workflow-run-v2.command.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; - -import { Command } from 'nest-commander'; -import { DataSource, Repository } from 'typeorm'; - -import { - ActiveOrSuspendedWorkspacesMigrationCommandRunner, - type RunOnWorkspaceArgs, -} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; -import { WorkspaceEntity } 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'; -import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util'; -import { WORKFLOW_RUN_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'; -import { WorkflowRunStatus } from 'src/modules/workflow/common/standard-objects/workflow-run.workspace-entity'; - -@Command({ - name: 'upgrade:1-2:add-enqueued-status-to-workflow-run-v2', - description: 'Add enqueued status to workflow run', -}) -export class AddEnqueuedStatusToWorkflowRunV2Command extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { - constructor( - @InjectRepository(WorkspaceEntity) - protected readonly workspaceRepository: Repository, - protected readonly twentyORMGlobalManager: TwentyORMGlobalManager, - @InjectRepository(ObjectMetadataEntity) - private readonly objectMetadataRepository: Repository, - @InjectRepository(FieldMetadataEntity) - private readonly fieldMetadataRepository: Repository, - @InjectDataSource() - private readonly coreDataSource: DataSource, - ) { - super(workspaceRepository, twentyORMGlobalManager); - } - - override async runOnWorkspace({ - workspaceId, - options, - }: RunOnWorkspaceArgs): Promise { - this.logger.log( - `Adding enqueued status to workflow run for workspace ${workspaceId}`, - ); - - const workflowRunObjectMetadata = - await this.objectMetadataRepository.findOne({ - where: { - workspaceId, - standardId: STANDARD_OBJECT_IDS.workflowRun, - }, - }); - - if (!workflowRunObjectMetadata) { - this.logger.error( - `Workflow run object metadata not found for workspace ${workspaceId}`, - ); - - return; - } - - const workflowRunStatusFieldMetadata = - await this.fieldMetadataRepository.findOne({ - where: { - standardId: WORKFLOW_RUN_STANDARD_FIELD_IDS.status, - objectMetadataId: workflowRunObjectMetadata.id, - }, - }); - - if (!workflowRunStatusFieldMetadata) { - this.logger.error( - `Workflow run status field metadata not found for workspace ${workspaceId}`, - ); - - return; - } - - const workflowRunStatusFieldMetadataOptions = - workflowRunStatusFieldMetadata.options; - - // check if enqueued status is already in the field metadata options - if ( - workflowRunStatusFieldMetadataOptions?.some( - (option) => option.value === WorkflowRunStatus.ENQUEUED, - ) - ) { - this.logger.log( - `Workflow run status field metadata options already contain enqueued status for workspace ${workspaceId}`, - ); - - return; - } else if (options.dryRun) { - this.logger.log( - `Would add enqueued status to workflow run status field metadata for workspace ${workspaceId}`, - ); - } else { - workflowRunStatusFieldMetadataOptions?.push({ - value: WorkflowRunStatus.ENQUEUED, - label: 'Enqueued', - position: 4, - color: 'blue', - }); - - await this.fieldMetadataRepository.save(workflowRunStatusFieldMetadata); - - this.logger.log( - `Enqueued status added to workflow run status field metadata for workspace ${workspaceId}`, - ); - } - - const schemaName = getWorkspaceSchemaName(workspaceId); - - if (options.dryRun) { - this.logger.log( - `Would try to add enqueued status to workflow run status enum for workspace ${workspaceId}`, - ); - } else { - try { - await this.coreDataSource.query( - `ALTER TYPE ${schemaName}."workflowRun_status_enum" ADD VALUE 'ENQUEUED'`, - ); - this.logger.log( - `Enqueued status added to workflow run status enum for workspace ${workspaceId}`, - ); - } catch (error) { - this.logger.error( - `Error adding enqueued status to workflow run status enum for workspace ${workspaceId}: ${error}`, - ); - } - } - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-2/1-2-add-next-step-ids-to-workflow-version-triggers.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-2/1-2-add-next-step-ids-to-workflow-version-triggers.command.ts deleted file mode 100644 index 1d741d6b95..0000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-2/1-2-add-next-step-ids-to-workflow-version-triggers.command.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { InjectRepository } from '@nestjs/typeorm'; - -import { Command } from 'nest-commander'; -import { Repository } from 'typeorm'; -import { isDefined } from 'twenty-shared/utils'; - -import { - ActiveOrSuspendedWorkspacesMigrationCommandRunner, - type RunOnWorkspaceArgs, -} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; -import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; -import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { type WorkflowVersionWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity'; -import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type'; - -@Command({ - name: 'upgrade:1-2:add-next-step-ids-to-workflow-version-triggers', - description: 'Add next step ids to workflow version triggers', -}) -export class AddNextStepIdsToWorkflowVersionTriggers extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { - constructor( - @InjectRepository(WorkspaceEntity) - protected readonly workspaceRepository: Repository, - protected readonly twentyORMGlobalManager: TwentyORMGlobalManager, - ) { - super(workspaceRepository, twentyORMGlobalManager); - } - - override async runOnWorkspace({ - workspaceId, - }: RunOnWorkspaceArgs): Promise { - const workflowVersionRepository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace( - workspaceId, - 'workflowVersion', - { shouldBypassPermissionChecks: true }, - ); - - const workflowVersions = await workflowVersionRepository.find(); - - for (const workflowVersion of workflowVersions) { - try { - const { trigger, steps } = workflowVersion; - - if (!isDefined(trigger)) { - this.logger.warn( - `Undefined trigger for workflowVersion ${workflowVersion.id}`, - ); - - continue; - } - - if (!isDefined(steps)) { - this.logger.warn( - `Undefined steps for workflowVersion ${workflowVersion.id}`, - ); - - continue; - } - - const rootSteps = this.getRootSteps(steps); - - if (rootSteps.length === 0) { - this.logger.warn( - `No root steps found for workflowVersion ${workflowVersion.id}`, - ); - - continue; - } - - await workflowVersionRepository.update(workflowVersion.id, { - trigger: { - ...trigger, - nextStepIds: rootSteps.map((step) => step.id), - }, - }); - } catch (error) { - this.logger.error( - `Error while adding nextStepIds to workflowVersion ${workflowVersion.id}`, - error, - ); - } - } - - this.logger.log(`${workflowVersions.length} triggers updated`); - } - - private getRootSteps(steps: WorkflowAction[]): WorkflowAction[] { - const childIds = new Set(); - - for (const step of steps) { - step.nextStepIds?.forEach((id) => childIds.add(id)); - } - - return steps.filter((step) => !childIds.has(step.id)); - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-2/1-2-remove-workflow-runs-without-state.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-2/1-2-remove-workflow-runs-without-state.command.ts deleted file mode 100644 index faefc6fd85..0000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-2/1-2-remove-workflow-runs-without-state.command.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { InjectRepository } from '@nestjs/typeorm'; - -import { Command } from 'nest-commander'; -import { IsNull, Repository } from 'typeorm'; - -import { - ActiveOrSuspendedWorkspacesMigrationCommandRunner, - type RunOnWorkspaceArgs, -} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; -import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; -import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { type WorkflowRunWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-run.workspace-entity'; - -@Command({ - name: 'migrate:1-2:remove-workflow-runs-without-state', - description: 'Remove workflow runs without state.', -}) -export class RemoveWorkflowRunsWithoutState extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { - constructor( - @InjectRepository(WorkspaceEntity) - protected readonly workspaceRepository: Repository, - protected readonly twentyORMGlobalManager: TwentyORMGlobalManager, - ) { - super(workspaceRepository, twentyORMGlobalManager); - } - - override async runOnWorkspace({ - workspaceId, - }: RunOnWorkspaceArgs): Promise { - const workflowRunRepository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace( - workspaceId, - 'workflowRun', - { shouldBypassPermissionChecks: true }, - ); - - await workflowRunRepository.delete({ state: IsNull() }); - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-2/1-2-upgrade-version-command.module.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-2/1-2-upgrade-version-command.module.ts deleted file mode 100644 index d76abdf884..0000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-2/1-2-upgrade-version-command.module.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; - -import { AddEnqueuedStatusToWorkflowRunV2Command } from 'src/database/commands/upgrade-version-command/1-2/1-2-add-enqueued-status-to-workflow-run-v2.command'; -import { AddNextStepIdsToWorkflowVersionTriggers } from 'src/database/commands/upgrade-version-command/1-2/1-2-add-next-step-ids-to-workflow-version-triggers.command'; -import { RemoveWorkflowRunsWithoutState } from 'src/database/commands/upgrade-version-command/1-2/1-2-remove-workflow-runs-without-state.command'; -import { WorkspaceEntity } 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 { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module'; - -@Module({ - imports: [ - TypeOrmModule.forFeature([ - WorkspaceEntity, - FieldMetadataEntity, - ObjectMetadataEntity, - ]), - WorkspaceDataSourceModule, - ], - providers: [ - RemoveWorkflowRunsWithoutState, - AddEnqueuedStatusToWorkflowRunV2Command, - AddNextStepIdsToWorkflowVersionTriggers, - ], - exports: [ - RemoveWorkflowRunsWithoutState, - AddEnqueuedStatusToWorkflowRunV2Command, - AddNextStepIdsToWorkflowVersionTriggers, - ], -}) -export class V1_2_UpgradeVersionCommandModule {} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-3/1-3-add-next-step-ids-to-workflow-runs-trigger.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-3/1-3-add-next-step-ids-to-workflow-runs-trigger.command.ts deleted file mode 100644 index 9c0a30ecf9..0000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-3/1-3-add-next-step-ids-to-workflow-runs-trigger.command.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; - -import { Command } from 'nest-commander'; -import { isDefined } from 'twenty-shared/utils'; -import { DataSource, Repository } from 'typeorm'; - -import { - ActiveOrSuspendedWorkspacesMigrationCommandRunner, - type RunOnWorkspaceArgs, -} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; -import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; -import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util'; -import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type'; - -@Command({ - name: 'upgrade:1-3:add-next-step-ids-to-workflow-runs-trigger', - description: 'Add next step ids to workflow runs trigger', -}) -export class AddNextStepIdsToWorkflowRunsTrigger extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { - constructor( - @InjectRepository(WorkspaceEntity) - protected readonly workspaceRepository: Repository, - @InjectDataSource() - private readonly coreDataSource: DataSource, - protected readonly twentyORMGlobalManager: TwentyORMGlobalManager, - ) { - super(workspaceRepository, twentyORMGlobalManager); - } - - override async runOnWorkspace({ - workspaceId, - }: RunOnWorkspaceArgs): Promise { - const schemaName = getWorkspaceSchemaName(workspaceId); - - const workflowRuns = await this.coreDataSource.query( - `SELECT id, state FROM ${schemaName}."workflowRun"`, - ); - - let updatedWorkflowRunCount = 0; - - for (const workflowRun of workflowRuns) { - try { - if (isDefined(workflowRun.state.flow.trigger.nextStepIds)) { - continue; - } - - const rootSteps = this.getRootSteps(workflowRun.state.flow.steps || []); - - const nextStepIds = rootSteps.map((step) => step.id); - - const updatedState = { - ...workflowRun.state, - flow: { - ...workflowRun.state.flow, - trigger: { - ...workflowRun.state.flow.trigger, - nextStepIds, - }, - }, - }; - - await this.coreDataSource.query( - `UPDATE ${schemaName}."workflowRun" SET state = $1::jsonb WHERE id = $2;`, - [updatedState, workflowRun.id], - ); - - updatedWorkflowRunCount += 1; - } catch (error) { - this.logger.error( - `Error while adding nextStepIds to workflowRun state '${workflowRun.id}'`, - error, - ); - } - } - this.logger.log( - `${updatedWorkflowRunCount}/${workflowRuns.length} triggers updated`, - ); - } - - private getRootSteps(steps: WorkflowAction[]): WorkflowAction[] { - const childIds = new Set(); - - for (const step of steps) { - step.nextStepIds?.forEach((id) => childIds.add(id)); - } - - return steps.filter((step) => !childIds.has(step.id)); - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-3/1-3-update-timestamp-column-type-in-workspace-schema.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-3/1-3-update-timestamp-column-type-in-workspace-schema.command.ts deleted file mode 100644 index e8f2ce6dfe..0000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-3/1-3-update-timestamp-column-type-in-workspace-schema.command.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; - -import { Command } from 'nest-commander'; -import { FieldMetadataType } from 'twenty-shared/types'; -import { DataSource, Repository } from 'typeorm'; - -import { - ActiveOrSuspendedWorkspacesMigrationCommandRunner, - type RunOnWorkspaceArgs, -} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; -import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; -import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; -import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util'; - -@Command({ - name: 'upgrade:1-3:update-timestamp-column-type-in-workspace-schema', - description: - 'Update the timestamp column type in all the workspace schema tables', -}) -export class UpdateTimestampColumnTypeInWorkspaceSchemaCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { - constructor( - @InjectRepository(WorkspaceEntity) - protected readonly workspaceRepository: Repository, - @InjectDataSource() - private readonly coreDataSource: DataSource, - protected readonly twentyORMGlobalManager: TwentyORMGlobalManager, - @InjectRepository(FieldMetadataEntity) - private readonly fieldMetadataRepository: Repository, - ) { - super(workspaceRepository, twentyORMGlobalManager); - } - - override async runOnWorkspace({ - workspaceId, - }: RunOnWorkspaceArgs): Promise { - const dateTimeFieldMetadataItems = await this.fieldMetadataRepository.find({ - where: { - workspaceId, - type: FieldMetadataType.DATE_TIME, - }, - relations: ['object'], - }); - - const schemaName = getWorkspaceSchemaName(workspaceId); - - for (const fieldMetadataItem of dateTimeFieldMetadataItems) { - this.logger.log( - `Updating column type for ${fieldMetadataItem.name} in ${schemaName}."${computeObjectTargetTable(fieldMetadataItem.object)}"`, - ); - await this.coreDataSource.query( - `ALTER TABLE ${schemaName}."${computeObjectTargetTable(fieldMetadataItem.object)}" - ALTER COLUMN "${fieldMetadataItem.name}" TYPE timestamptz(3);`, - ); - } - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-3/1-3-upgrade-version-command.module.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-3/1-3-upgrade-version-command.module.ts deleted file mode 100644 index c8e589ab9c..0000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-3/1-3-upgrade-version-command.module.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; - -import { AddNextStepIdsToWorkflowRunsTrigger } from 'src/database/commands/upgrade-version-command/1-3/1-3-add-next-step-ids-to-workflow-runs-trigger.command'; -import { UpdateTimestampColumnTypeInWorkspaceSchemaCommand } from 'src/database/commands/upgrade-version-command/1-3/1-3-update-timestamp-column-type-in-workspace-schema.command'; -import { ApiKeyEntity } from 'src/engine/core-modules/api-key/api-key.entity'; -import { ApiKeyModule } from 'src/engine/core-modules/api-key/api-key.module'; -import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; -import { WorkspaceEntity } 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 { RoleTargetsEntity } from 'src/engine/metadata-modules/role/role-targets.entity'; -import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; -import { RoleModule } from 'src/engine/metadata-modules/role/role.module'; -import { WorkspaceFeatureFlagsMapCacheModule } from 'src/engine/metadata-modules/workspace-feature-flags-map-cache/workspace-feature-flags-map-cache.module'; -import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.module'; -import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module'; -import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module'; - -@Module({ - imports: [ - TypeOrmModule.forFeature([ - WorkspaceEntity, - ApiKeyEntity, - FieldMetadataEntity, - ObjectMetadataEntity, - RoleEntity, - RoleTargetsEntity, - ]), - WorkspaceDataSourceModule, - ApiKeyModule, - FeatureFlagModule, - TwentyORMModule, - RoleModule, - WorkspacePermissionsCacheModule, - WorkspaceFeatureFlagsMapCacheModule, - ], - providers: [ - AddNextStepIdsToWorkflowRunsTrigger, - UpdateTimestampColumnTypeInWorkspaceSchemaCommand, - ], - exports: [ - AddNextStepIdsToWorkflowRunsTrigger, - UpdateTimestampColumnTypeInWorkspaceSchemaCommand, - ], -}) -export class V1_3_UpgradeVersionCommandModule {} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-5/1-5-add-positions-to-workflow-versions-and-workflow-runs.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-5/1-5-add-positions-to-workflow-versions-and-workflow-runs.command.ts deleted file mode 100644 index 1f5a6392f9..0000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-5/1-5-add-positions-to-workflow-versions-and-workflow-runs.command.ts +++ /dev/null @@ -1,333 +0,0 @@ -import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; - -import Dagre from '@dagrejs/dagre'; -import { Command, Option } from 'nest-commander'; -import { isDefined } from 'twenty-shared/utils'; -import { DataSource, Repository } from 'typeorm'; -import { v4 } from 'uuid'; - -import { - ActiveOrSuspendedWorkspacesMigrationCommandOptions, - ActiveOrSuspendedWorkspacesMigrationCommandRunner, - type RunOnWorkspaceArgs, -} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; -import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; -import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util'; -import { type WorkflowVersionWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity'; -import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type'; -import { type WorkflowTrigger } from 'src/modules/workflow/workflow-trigger/types/workflow-trigger.type'; - -type Node = { - id: string; - position: { x: number; y: number }; - size: number; - measured?: { width: number; height: number }; -}; - -type Edge = { id: string; source: string; target: string }; - -type Diagram = { - nodes: Node[]; - edges: Edge[]; -}; - -export type AddPositionsToWorkflowVersionsAndWorkflowRunsOptions = - ActiveOrSuspendedWorkspacesMigrationCommandOptions & { - processWorkflowRuns?: boolean; - }; - -@Command({ - name: 'upgrade:1-5:add-positions-to-workflow-versions-and-workflow-runs', - description: 'Add positions to workflow versions and workflow runs', -}) -export class AddPositionsToWorkflowVersionsAndWorkflowRunsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { - constructor( - @InjectRepository(WorkspaceEntity) - protected readonly workspaceRepository: Repository, - @InjectDataSource() - private readonly coreDataSource: DataSource, - protected readonly twentyORMGlobalManager: TwentyORMGlobalManager, - ) { - super(workspaceRepository, twentyORMGlobalManager); - } - - @Option({ - flags: '--process-workflow-runs [process_workflow_runs]', - description: 'Process workflowRuns positions (default false)', - required: false, - }) - parseProcessWorkflowRuns(): boolean { - return true; - } - - override async runOnWorkspace({ - workspaceId, - options, - }: Omit & { - options: AddPositionsToWorkflowVersionsAndWorkflowRunsOptions; - }): Promise { - await this.addPositionsToWorkflowVersions({ workspaceId }); - - if (options.processWorkflowRuns) { - await this.addPositionsToWorkflowRuns({ workspaceId }); - } - } - - private async addPositionsToWorkflowVersions({ - workspaceId, - }: { - workspaceId: string; - }) { - const workflowVersionRepository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace( - workspaceId, - 'workflowVersion', - { shouldBypassPermissionChecks: true }, - ); - - const workflowVersions = await workflowVersionRepository.find(); - - let count = 0; - - for (const workflowVersion of workflowVersions) { - try { - // We only update one branch workflow - if (this.isWorkflowMultiBranch(workflowVersion)) { - continue; - } - - const { updatedTrigger, updatedSteps } = - this.getOrganizedStepsAndTrigger({ - trigger: workflowVersion.trigger, - steps: workflowVersion.steps, - }); - - await workflowVersionRepository.update(workflowVersion.id, { - trigger: updatedTrigger, - steps: updatedSteps, - }); - count += 1; - } catch (error) { - this.logger.error( - `Error while adding positions to workflowVersion '${workflowVersion.id}'`, - error, - ); - } - } - - this.logger.log( - `Workflow versions updated count: ${count} out of ${workflowVersions.length}`, - ); - } - - private isWorkflowMultiBranch( - workflowVersion: WorkflowVersionWorkspaceEntity, - ) { - if ((workflowVersion.trigger?.nextStepIds ?? []).length > 1) { - return true; - } - - for (const step of workflowVersion.steps || []) { - if ((step.nextStepIds ?? []).length > 1) { - return true; - } - } - - return false; - } - - private async addPositionsToWorkflowRuns({ - workspaceId, - }: { - workspaceId: string; - }) { - const schemaName = getWorkspaceSchemaName(workspaceId); - - const workflowRuns = await this.coreDataSource.query( - `SELECT id, state FROM ${schemaName}."workflowRun"`, - ); - - for (const workflowRun of workflowRuns) { - try { - const { updatedTrigger, updatedSteps } = - this.getOrganizedStepsAndTrigger({ - trigger: workflowRun.state.flow.trigger, - steps: workflowRun.state.flow.steps, - }); - - const updatedState = { - ...workflowRun.state, - flow: { - ...workflowRun.state.flow, - trigger: updatedTrigger, - steps: updatedSteps, - }, - }; - - await this.coreDataSource.query( - `UPDATE ${schemaName}."workflowRun" SET state = $1::jsonb WHERE id = $2`, - [updatedState, workflowRun.id], - ); - } catch (error) { - this.logger.error( - `Error while adding positions to workflowRuns '${workflowRun.id}'`, - error, - ); - } - } - } - - private getNodePositionFromDiagram({ - stepId, - diagram, - }: { - stepId: string; - diagram: Diagram; - }) { - return diagram.nodes.find((node) => node.id === stepId)?.position; - } - - private createWorkflowDiagram({ - trigger, - steps, - }: { - trigger?: WorkflowTrigger | null; - steps: WorkflowAction[] | null; - }): Diagram { - const nodes: Node[] = []; - - const edges: Edge[] = []; - - if (!isDefined(trigger)) { - const triggerNextStepIds = isDefined(steps) - ? this.getRootSteps(steps).map((step) => step.id) - : []; - - triggerNextStepIds.forEach((stepId: string) => { - edges.push({ - id: v4(), - source: 'trigger', - target: stepId, - }); - }); - nodes.push({ id: 'trigger', size: 13, position: { x: 0, y: 0 } }); - } else { - nodes.push({ - id: 'trigger', - size: Math.min(trigger.name.length, 29), - position: { x: 0, y: 0 }, - }); - } - - for (const step of steps || []) { - nodes.push({ - id: step.id, - size: Math.min(step.name.length, 29), - position: { x: 0, y: 0 }, - }); - - step.nextStepIds?.forEach((nextStepId) => { - edges.push({ - id: v4(), - source: step.id, - target: nextStepId, - }); - }); - } - - for (const stepId of trigger?.nextStepIds || []) { - edges.push({ - id: v4(), - source: 'trigger', - target: stepId, - }); - } - - return { - nodes, - edges, - }; - } - - private getOrganizedStepsAndTrigger({ - trigger, - steps, - }: { - trigger?: WorkflowTrigger | null; - steps: WorkflowAction[] | null; - }) { - const workflowDiagram = this.createWorkflowDiagram({ steps, trigger }); - - const organizedDiagram = this.getOrganizedDiagram(workflowDiagram); - - const updatedTrigger: WorkflowTrigger | undefined = isDefined(trigger) - ? { - ...trigger, - position: this.getNodePositionFromDiagram({ - stepId: 'trigger', - diagram: organizedDiagram, - }), - } - : undefined; - - const updatedSteps: WorkflowAction[] = (steps || []).map( - (step: WorkflowAction) => ({ - ...step, - position: this.getNodePositionFromDiagram({ - stepId: step.id, - diagram: organizedDiagram, - }), - }), - ); - - return { - updatedTrigger, - updatedSteps, - }; - } - - private getRootSteps(steps: WorkflowAction[]): WorkflowAction[] { - const childIds = new Set(); - - for (const step of steps) { - step.nextStepIds?.forEach((id) => childIds.add(id)); - } - - return steps.filter((step) => !childIds.has(step.id)); - } - - private getOrganizedDiagram(diagram: Diagram): Diagram { - const graph = new Dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({})); - - graph.setGraph({ - ranksep: 80, // Vertical distance between 2 nodes - nodesep: 200, // Horizontal distance between 2 nodes - rankdir: 'TB', - }); - - diagram.edges.forEach((edge) => graph.setEdge(edge.source, edge.target)); - diagram.nodes.forEach((node) => - graph.setNode(node.id, { - width: node.size * 6, - height: 50, - }), - ); - - Dagre.layout(graph); - - return { - nodes: diagram.nodes.map((node) => { - const position = graph.node(node.id); - - // We are shifting the dagre node position (anchor=center center) to the top left - // so it matches the React Flow node anchor point (top left). - const x = position.x - position.width / 2; - const y = position.y - position.height / 2; - - return { ...node, position: { x, y } }; - }), - edges: diagram.edges, - }; - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-5/1-5-migrate-views-to-core.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-5/1-5-migrate-views-to-core.command.ts deleted file mode 100644 index f46f4beaa0..0000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-5/1-5-migrate-views-to-core.command.ts +++ /dev/null @@ -1,671 +0,0 @@ -import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; - -import { Command } from 'nest-commander'; -import { type ViewFilterOperand as SharedViewFilterOperand } from 'twenty-shared/types'; -import { convertViewFilterOperandToCoreOperand } from 'twenty-shared/utils'; -import { DataSource, In, Repository, type QueryRunner } from 'typeorm'; -import { type QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; - -import { - ActiveOrSuspendedWorkspacesMigrationCommandRunner, - type RunOnWorkspaceArgs, -} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; -import { WorkspaceEntity } 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 { ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity'; -import { ViewFilterGroupEntity } from 'src/engine/metadata-modules/view-filter-group/entities/view-filter-group.entity'; -import { type ViewFilterGroupLogicalOperator } from 'src/engine/metadata-modules/view-filter-group/enums/view-filter-group-logical-operator'; -import { ViewFilterEntity } from 'src/engine/metadata-modules/view-filter/entities/view-filter.entity'; -import { ViewGroupEntity } from 'src/engine/metadata-modules/view-group/entities/view-group.entity'; -import { ViewSortEntity } from 'src/engine/metadata-modules/view-sort/entities/view-sort.entity'; -import { type ViewSortDirection } from 'src/engine/metadata-modules/view-sort/enums/view-sort-direction'; -import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity'; -import { ViewKey } from 'src/engine/metadata-modules/view/enums/view-key.enum'; -import { ViewOpenRecordIn } from 'src/engine/metadata-modules/view/enums/view-open-record-in'; -import { ViewType } from 'src/engine/metadata-modules/view/enums/view-type.enum'; -import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { ViewFieldWorkspaceEntity } from 'src/modules/view/standard-objects/view-field.workspace-entity'; -import { type ViewFilterGroupWorkspaceEntity } from 'src/modules/view/standard-objects/view-filter-group.workspace-entity'; -import { type ViewFilterWorkspaceEntity } from 'src/modules/view/standard-objects/view-filter.workspace-entity'; -import { type ViewGroupWorkspaceEntity } from 'src/modules/view/standard-objects/view-group.workspace-entity'; -import { type ViewSortWorkspaceEntity } from 'src/modules/view/standard-objects/view-sort.workspace-entity'; -import { type ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity'; -import { convertViewFilterWorkspaceValueToCoreValue } from 'src/modules/view/utils/convert-view-filter-workspace-value-to-core-value'; - -@Command({ - name: 'upgrade:1-5:migrate-views-to-core', - description: 'Migrate views from workspace schemas to core schema', -}) -export class MigrateViewsToCoreCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { - constructor( - @InjectRepository(WorkspaceEntity) - protected readonly workspaceRepository: Repository, - protected readonly twentyORMGlobalManager: TwentyORMGlobalManager, - @InjectDataSource() - private readonly coreDataSource: DataSource, - ) { - super(workspaceRepository, twentyORMGlobalManager); - } - - override async runOnWorkspace({ - index, - total, - workspaceId, - options, - }: RunOnWorkspaceArgs): Promise { - this.logger.log( - `Migrating views to core schema for workspace ${workspaceId} ${index + 1}/${total}`, - ); - - const queryRunner = this.coreDataSource.createQueryRunner(); - - await queryRunner.connect(); - - try { - await queryRunner.startTransaction(); - - try { - await this.migrateViews( - workspaceId, - options.dryRun ?? false, - queryRunner, - ); - - if (options.dryRun) { - this.logger.log( - `DRY RUN: Would migrate views to core schema for workspace ${workspaceId}`, - ); - } else { - await queryRunner.commitTransaction(); - this.logger.log( - `Successfully migrated views to core schema for workspace ${workspaceId}`, - ); - } - } catch (error) { - if (queryRunner.isTransactionActive) { - await queryRunner.rollbackTransaction(); - this.logger.error( - `Transaction rolled back for workspace ${workspaceId} due to error: ${error.message}`, - ); - } - - throw error; - } - } catch (error) { - this.logger.error( - `Failed to migrate views to core schema for workspace ${workspaceId}: ${error.message}`, - ); - - throw error; - } finally { - await queryRunner.release(); - } - } - - private async migrateViews( - workspaceId: string, - dryRun: boolean, - queryRunner: QueryRunner, - ): Promise { - const workspaceViewRepository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace( - workspaceId, - 'view', - { shouldBypassPermissionChecks: true }, - ); - - const workspaceViews = await workspaceViewRepository.find({ - relations: [ - 'viewFields', - 'viewFilters', - 'viewSorts', - 'viewGroups', - 'viewFilterGroups', - ], - withDeleted: true, - }); - - const { - corruptedViewIds, - corruptedViewFieldIds, - corruptedViewFilterIds, - corruptedViewSortIds, - corruptedViewGroupIds, - } = await this.fetchAndDeleteCorruptedViewResources( - workspaceId, - workspaceViews, - queryRunner, - dryRun, - ); - - const workspaceViewWithoutOrphansResources = workspaceViews - .filter((view) => !corruptedViewIds.includes(view.id)) - .map((view) => ({ - ...view, - viewFields: view.viewFields.filter( - (viewField) => !corruptedViewFieldIds.includes(viewField.id), - ), - viewFilters: view.viewFilters.filter( - (viewFilter) => !corruptedViewFilterIds.includes(viewFilter.id), - ), - viewSorts: view.viewSorts.filter( - (viewSort) => !corruptedViewSortIds.includes(viewSort.id), - ), - viewGroups: view.viewGroups.filter( - (viewGroup) => !corruptedViewGroupIds.includes(viewGroup.id), - ), - })); - - if (workspaceViewWithoutOrphansResources.length === 0) { - this.logger.log(`No views to migrate for workspace ${workspaceId}`); - - return; - } - - this.logger.log( - `${dryRun ? 'DRY RUN: ' : ''}Found ${workspaceViews.length} views to migrate for workspace ${workspaceId}`, - ); - - if (dryRun) { - for (const view of workspaceViewWithoutOrphansResources) { - const deletedStatus = view.deletedAt ? ' (DELETED)' : ''; - - this.logger.log( - `DRY RUN: Would migrate view ${view.id} (${view.name})${deletedStatus} with ${view.viewFields.length} fields, ${view.viewFilters.length} filters, ${view.viewSorts.length} sorts, ${view.viewGroups.length} groups, ${view.viewFilterGroups.length} filter groups`, - ); - } - - return; - } - - await this.deleteExistingCoreViewObjects(workspaceId, queryRunner, dryRun); - - for (const workspaceView of workspaceViewWithoutOrphansResources) { - await this.migrateViewEntity(workspaceView, workspaceId, queryRunner); - - if (workspaceView.viewFields?.length > 0) { - await this.migrateViewFields( - workspaceView.viewFields, - workspaceId, - queryRunner, - ); - } - - if (workspaceView.viewFilterGroups?.length > 0) { - await this.migrateViewFilterGroups( - workspaceView.viewFilterGroups, - workspaceId, - queryRunner, - ); - } - - if (workspaceView.viewFilters?.length > 0) { - await this.migrateViewFilters( - workspaceView.viewFilters, - workspaceId, - queryRunner, - ); - } - - if (workspaceView.viewSorts?.length > 0) { - await this.migrateViewSorts( - workspaceView.viewSorts, - workspaceId, - queryRunner, - ); - } - - if (workspaceView.viewGroups?.length > 0) { - await this.migrateViewGroups( - workspaceView.viewGroups, - workspaceId, - queryRunner, - ); - } - - const deletedStatus = workspaceView.deletedAt ? ' (DELETED)' : ''; - - this.logger.log( - `Migrated view ${workspaceView.id} (${workspaceView.name})${deletedStatus} to core schema`, - ); - } - } - - private async fetchAndDeleteCorruptedViewResources( - workspaceId: string, - views: ViewWorkspaceEntity[], - queryRunner: QueryRunner, - dryRun: boolean, - ): Promise<{ - corruptedViewIds: string[]; - corruptedViewFieldIds: string[]; - corruptedViewFilterIds: string[]; - corruptedViewSortIds: string[]; - corruptedViewGroupIds: string[]; - }> { - const fieldMetadataIdsUsedInViewFields = views - .flatMap((view) => view.viewFields) - .map((viewField) => viewField.fieldMetadataId); - - const fieldMetadataIdsUsedInViewFilters = views - .flatMap((view) => view.viewFilters) - .map((viewFilter) => viewFilter.fieldMetadataId); - - const fieldMetadataIdsUsedInViewSorts = views - .flatMap((view) => view.viewSorts) - .map((viewSort) => viewSort.fieldMetadataId); - - const fieldMetadataIdsUsedInViewGroups = views - .flatMap((view) => view.viewGroups) - .map((viewGroup) => viewGroup.fieldMetadataId); - - const fieldMetadataIds = [ - ...fieldMetadataIdsUsedInViewFields, - ...fieldMetadataIdsUsedInViewFilters, - ...fieldMetadataIdsUsedInViewSorts, - ...fieldMetadataIdsUsedInViewGroups, - ]; - - const fieldMetadataRepository = - queryRunner.manager.getRepository(FieldMetadataEntity); - const fieldMetadataItems = await fieldMetadataRepository.find({ - where: { - id: In(fieldMetadataIds), - }, - }); - - const existingFieldMetadataIds = fieldMetadataItems.map( - (fieldMetadata) => fieldMetadata.id, - ); - - const corruptedViewFieldIds = views - .flatMap((view) => view.viewFields) - .filter( - (viewField) => - !existingFieldMetadataIds.includes(viewField.fieldMetadataId) || - viewField.position === null, - ) - .map((viewField) => viewField.id); - - const corruptedViewFilterIds = views - .flatMap((view) => view.viewFilters) - .filter( - (viewFilter) => - !existingFieldMetadataIds.includes(viewFilter.fieldMetadataId), - ) - .map((viewFilter) => viewFilter.id); - - const corruptedViewSortIds = views - .flatMap((view) => view.viewSorts) - .filter( - (viewSort) => - !existingFieldMetadataIds.includes(viewSort.fieldMetadataId), - ) - .map((viewSort) => viewSort.id); - - const corruptedViewGroupIds = views - .flatMap((view) => view.viewGroups) - .filter( - (viewGroup) => - !existingFieldMetadataIds.includes(viewGroup.fieldMetadataId), - ) - .map((viewGroup) => viewGroup.id); - - const workspaceViewFieldRepository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace( - workspaceId, - 'viewField', - { shouldBypassPermissionChecks: true }, - ); - - if (!dryRun) { - await workspaceViewFieldRepository.delete({ - id: In(corruptedViewFieldIds), - }); - } - - this.logger.log( - `Deleted ${corruptedViewFieldIds.length} out of ${views.flatMap((view) => view.viewFields).length} view fields that have no corresponding field metadata for workspace ${workspaceId}`, - ); - - const workspaceViewFilterRepository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace( - workspaceId, - 'viewFilter', - { shouldBypassPermissionChecks: true }, - ); - - if (!dryRun) { - await workspaceViewFilterRepository.delete({ - id: In(corruptedViewFilterIds), - }); - } - - this.logger.log( - `Deleted ${corruptedViewFilterIds.length} out of ${views.flatMap((view) => view.viewFilters).length} view filters that have no corresponding field metadata for workspace ${workspaceId}`, - ); - - const workspaceViewSortRepository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace( - workspaceId, - 'viewSort', - { shouldBypassPermissionChecks: true }, - ); - - if (!dryRun) { - await workspaceViewSortRepository.delete({ - id: In(corruptedViewSortIds), - }); - } - - this.logger.log( - `Deleted ${corruptedViewSortIds.length} out of ${views.flatMap((view) => view.viewSorts).length} view sorts that have no corresponding field metadata for workspace ${workspaceId}`, - ); - - const workspaceViewGroupRepository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace( - workspaceId, - 'viewGroup', - { shouldBypassPermissionChecks: true }, - ); - - if (!dryRun) { - await workspaceViewGroupRepository.delete({ - id: In(corruptedViewGroupIds), - }); - } - - this.logger.log( - `Deleted ${corruptedViewGroupIds.length} out of ${views.flatMap((view) => view.viewGroups).length} view groups that have no corresponding field metadata for workspace ${workspaceId}`, - ); - - const objectMetadataIdsUsedInViews = views.map( - (view) => view.objectMetadataId, - ); - - const objectMetadataRepository = - queryRunner.manager.getRepository(ObjectMetadataEntity); - const objectMetadataItems = await objectMetadataRepository.find({ - where: { - id: In(objectMetadataIdsUsedInViews), - }, - }); - - const existingObjectMetadataIds = objectMetadataItems.map( - (objectMetadata) => objectMetadata.id, - ); - - const corruptedViewIds = views - .filter( - (view) => !existingObjectMetadataIds.includes(view.objectMetadataId), - ) - .map((view) => view.id); - - const workspaceViewRepository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace( - workspaceId, - 'view', - { shouldBypassPermissionChecks: true }, - ); - - if (!dryRun) { - await workspaceViewRepository.delete({ - id: In(corruptedViewIds), - }); - } - - this.logger.log( - `Deleted ${corruptedViewIds.length} out of ${views.length} views that have no corresponding object metadata for workspace ${workspaceId}`, - ); - - return { - corruptedViewIds, - corruptedViewFieldIds, - corruptedViewFilterIds, - corruptedViewSortIds, - corruptedViewGroupIds, - }; - } - - private async deleteExistingCoreViewObjects( - workspaceId: string, - queryRunner: QueryRunner, - dryRun: boolean, - ): Promise { - const viewRepository = queryRunner.manager.getRepository(ViewEntity); - const existingViews = await viewRepository.find({ - where: { workspaceId }, - select: ['id'], - withDeleted: true, - }); - - if (existingViews.length === 0) { - this.logger.log( - `No existing core view objects found for workspace ${workspaceId}`, - ); - - return; - } - - this.logger.log( - `${dryRun ? 'DRY RUN: ' : ''}Deleting ${existingViews.length} existing core view objects for workspace ${workspaceId}`, - ); - - if (dryRun) { - this.logger.log( - `DRY RUN: Would delete all existing core view objects for workspace ${workspaceId}`, - ); - - return; - } - - await viewRepository.delete({ workspaceId }); - - this.logger.log( - `Deleted all existing core view objects for workspace ${workspaceId}`, - ); - } - - private async migrateViewEntity( - workspaceView: ViewWorkspaceEntity, - workspaceId: string, - queryRunner: QueryRunner, - ): Promise { - let viewName = workspaceView.name; - - if (workspaceView.key === 'INDEX' && !viewName.includes('{')) { - viewName = 'All {objectLabelPlural}'; - } - - const coreView: QueryDeepPartialEntity = { - id: workspaceView.id, - name: viewName, - objectMetadataId: workspaceView.objectMetadataId, - type: workspaceView.type === 'table' ? ViewType.TABLE : ViewType.KANBAN, - key: - workspaceView.key === 'INDEX' || workspaceView.key === ViewKey.INDEX - ? ViewKey.INDEX - : null, - icon: workspaceView.icon, - position: workspaceView.position, - isCompact: workspaceView.isCompact, - isCustom: workspaceView.key !== 'INDEX', - openRecordIn: - workspaceView.openRecordIn === 'SIDE_PANEL' - ? ViewOpenRecordIn.SIDE_PANEL - : ViewOpenRecordIn.RECORD_PAGE, - kanbanAggregateOperation: workspaceView.kanbanAggregateOperation, - kanbanAggregateOperationFieldMetadataId: - workspaceView.kanbanAggregateOperationFieldMetadataId, - workspaceId, - createdAt: new Date(workspaceView.createdAt), - updatedAt: new Date(workspaceView.updatedAt), - deletedAt: workspaceView.deletedAt - ? new Date(workspaceView.deletedAt) - : null, - anyFieldFilterValue: workspaceView.anyFieldFilterValue, - }; - - const repository = queryRunner.manager.getRepository(ViewEntity); - - await repository.insert(coreView); - } - - private async migrateViewFields( - workspaceViewFields: ViewFieldWorkspaceEntity[], - workspaceId: string, - queryRunner: QueryRunner, - ): Promise { - for (const field of workspaceViewFields) { - const coreViewField: QueryDeepPartialEntity = { - id: field.id, - fieldMetadataId: field.fieldMetadataId, - viewId: field.viewId, - position: field.position, - isVisible: field.isVisible, - size: field.size, - workspaceId, - createdAt: new Date(field.createdAt), - updatedAt: new Date(field.updatedAt), - deletedAt: field.deletedAt ? new Date(field.deletedAt) : null, - }; - - const repository = queryRunner.manager.getRepository(ViewFieldEntity); - - await repository.insert(coreViewField); - } - } - - private async migrateViewFilters( - workspaceViewFilters: ViewFilterWorkspaceEntity[], - workspaceId: string, - queryRunner: QueryRunner, - ): Promise { - for (const filter of workspaceViewFilters) { - if (!filter.viewId) { - this.logger.warn( - `Skipping view filter ${filter.id} with null viewId for workspace ${workspaceId}`, - ); - continue; - } - - const coreViewFilter: QueryDeepPartialEntity = { - id: filter.id, - fieldMetadataId: filter.fieldMetadataId, - viewId: filter.viewId, - operand: convertViewFilterOperandToCoreOperand( - filter.operand as SharedViewFilterOperand, - ), - value: convertViewFilterWorkspaceValueToCoreValue(filter.value), - viewFilterGroupId: filter.viewFilterGroupId, - workspaceId, - createdAt: new Date(filter.createdAt), - updatedAt: new Date(filter.updatedAt), - deletedAt: filter.deletedAt ? new Date(filter.deletedAt) : null, - subFieldName: filter.subFieldName, - }; - - const repository = queryRunner.manager.getRepository(ViewFilterEntity); - - await repository.insert(coreViewFilter); - } - } - - private async migrateViewSorts( - workspaceViewSorts: ViewSortWorkspaceEntity[], - workspaceId: string, - queryRunner: QueryRunner, - ): Promise { - for (const sort of workspaceViewSorts) { - if (!sort.viewId) { - this.logger.warn( - `Skipping view sort ${sort.id} with null viewId for workspace ${workspaceId}`, - ); - continue; - } - - const direction = sort.direction.toUpperCase() as ViewSortDirection; - - const coreViewSort: QueryDeepPartialEntity = { - id: sort.id, - fieldMetadataId: sort.fieldMetadataId, - viewId: sort.viewId, - direction: direction, - workspaceId, - createdAt: new Date(sort.createdAt), - updatedAt: new Date(sort.updatedAt), - deletedAt: sort.deletedAt ? new Date(sort.deletedAt) : null, - }; - - const repository = queryRunner.manager.getRepository(ViewSortEntity); - - await repository.insert(coreViewSort); - } - } - - private async migrateViewGroups( - workspaceViewGroups: ViewGroupWorkspaceEntity[], - workspaceId: string, - queryRunner: QueryRunner, - ): Promise { - for (const group of workspaceViewGroups) { - if (!group.viewId) { - this.logger.warn( - `Skipping view group ${group.id} with null viewId for workspace ${workspaceId}`, - ); - continue; - } - - const coreViewGroup: QueryDeepPartialEntity = { - id: group.id, - fieldMetadataId: group.fieldMetadataId, - viewId: group.viewId, - fieldValue: group.fieldValue, - isVisible: group.isVisible, - position: group.position, - workspaceId, - createdAt: new Date(group.createdAt), - updatedAt: new Date(group.updatedAt), - deletedAt: group.deletedAt ? new Date(group.deletedAt) : null, - }; - - const repository = queryRunner.manager.getRepository(ViewGroupEntity); - - await repository.insert(coreViewGroup); - } - } - - private async migrateViewFilterGroups( - workspaceViewFilterGroups: ViewFilterGroupWorkspaceEntity[], - workspaceId: string, - queryRunner: QueryRunner, - ): Promise { - for (const filterGroup of workspaceViewFilterGroups.sort( - (a, b) => - new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime(), - )) { - const coreViewFilterGroup: QueryDeepPartialEntity = - { - id: filterGroup.id, - viewId: filterGroup.viewId, - logicalOperator: - filterGroup.logicalOperator as ViewFilterGroupLogicalOperator, - parentViewFilterGroupId: filterGroup.parentViewFilterGroupId, - positionInViewFilterGroup: filterGroup.positionInViewFilterGroup, - workspaceId, - createdAt: new Date(filterGroup.createdAt), - updatedAt: new Date(filterGroup.updatedAt), - deletedAt: filterGroup.deletedAt - ? new Date(filterGroup.deletedAt) - : null, - }; - - const repository = queryRunner.manager.getRepository( - ViewFilterGroupEntity, - ); - - await repository.insert(coreViewFilterGroup); - } - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-5/1-5-remove-favorite-view-relation.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-5/1-5-remove-favorite-view-relation.command.ts deleted file mode 100644 index 03d388634a..0000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-5/1-5-remove-favorite-view-relation.command.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; - -import { Command } from 'nest-commander'; -import { FieldMetadataType } from 'twenty-shared/types'; -import { DataSource, Repository } from 'typeorm'; - -import { - ActiveOrSuspendedWorkspacesMigrationCommandRunner, - type RunOnWorkspaceArgs, -} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; -import { WorkspaceEntity } 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 { WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service'; -import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util'; -import { - FAVORITE_STANDARD_FIELD_IDS, - VIEW_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'; - -@Command({ - name: 'upgrade:1-5:remove-favorite-view-relation', - description: 'Remove favorite view relation.', -}) -export class RemoveFavoriteViewRelationCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { - constructor( - @InjectRepository(WorkspaceEntity) - protected readonly workspaceRepository: Repository, - protected readonly twentyORMGlobalManager: TwentyORMGlobalManager, - protected readonly workspaceSchemaManager: WorkspaceSchemaManagerService, - @InjectDataSource() - protected readonly coreDataSource: DataSource, - private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService, - ) { - super(workspaceRepository, twentyORMGlobalManager); - } - - override async runOnWorkspace({ - workspaceId, - options, - }: RunOnWorkspaceArgs): Promise { - const queryRunner = this.coreDataSource.createQueryRunner(); - - await queryRunner.connect(); - - await queryRunner.startTransaction(); - - try { - const objectMetadataRepository = - queryRunner.manager.getRepository(ObjectMetadataEntity); - - const fieldMetadataRepository = - queryRunner.manager.getRepository(FieldMetadataEntity); - - const [favoriteObjectMetadata] = await objectMetadataRepository.find({ - where: { - standardId: STANDARD_OBJECT_IDS.favorite, - workspaceId, - }, - }); - - if (!favoriteObjectMetadata) { - throw new Error('Favorite object metadata not found'); - } - - const [viewObjectMetadata] = await objectMetadataRepository.find({ - where: { - standardId: STANDARD_OBJECT_IDS.view, - workspaceId, - }, - }); - - if (!viewObjectMetadata) { - throw new Error('View object metadata not found'); - } - - const [favoriteViewFieldMetadata] = await fieldMetadataRepository.find({ - where: { - objectMetadataId: favoriteObjectMetadata.id, - standardId: FAVORITE_STANDARD_FIELD_IDS.view, - workspaceId, - }, - }); - - const [viewFavoriteFieldMetadata] = await fieldMetadataRepository.find({ - where: { - objectMetadataId: viewObjectMetadata.id, - standardId: VIEW_STANDARD_FIELD_IDS.favorites, - workspaceId, - }, - }); - - if (!viewFavoriteFieldMetadata || !favoriteViewFieldMetadata) { - this.logger.warn( - 'View or favorite view field metadata not found or already migrated, skipping...', - ); - - return; - } - - if (!options.dryRun) { - await fieldMetadataRepository.delete(viewFavoriteFieldMetadata.id); - await fieldMetadataRepository.update(favoriteViewFieldMetadata.id, { - name: 'viewId', - type: FieldMetadataType.UUID, - label: 'ViewId', - description: 'ViewId', - icon: 'IconView', - isSystem: true, - isNullable: true, - relationTargetFieldMetadataId: null, - relationTargetObjectMetadataId: null, - settings: null, - }); - } - - const workspaceSchemaName = getWorkspaceSchemaName(workspaceId); - - const foreignKeyName = - queryRunner.connection.namingStrategy.foreignKeyName( - favoriteObjectMetadata.nameSingular, - [`${favoriteViewFieldMetadata.name}Id`], - `${workspaceSchemaName}.${viewObjectMetadata.nameSingular}`, - [`${viewFavoriteFieldMetadata.name}`], - ); - - if (!options.dryRun) { - await this.workspaceSchemaManager.foreignKeyManager.dropForeignKey({ - queryRunner, - schemaName: workspaceSchemaName, - tableName: favoriteObjectMetadata.nameSingular, - foreignKeyName, - }); - } - - await queryRunner.commitTransaction(); - } catch (error) { - await queryRunner.rollbackTransaction(); - throw error; - } finally { - await queryRunner.release(); - } - - await this.workspaceMetadataVersionService.incrementMetadataVersion( - workspaceId, - ); - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-5/1-5-upgrade-version-command.module.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-5/1-5-upgrade-version-command.module.ts deleted file mode 100644 index 203c7c3314..0000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-5/1-5-upgrade-version-command.module.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; - -import { AddPositionsToWorkflowVersionsAndWorkflowRunsCommand } from 'src/database/commands/upgrade-version-command/1-5/1-5-add-positions-to-workflow-versions-and-workflow-runs.command'; -import { MigrateViewsToCoreCommand } from 'src/database/commands/upgrade-version-command/1-5/1-5-migrate-views-to-core.command'; -import { RemoveFavoriteViewRelationCommand } from 'src/database/commands/upgrade-version-command/1-5/1-5-remove-favorite-view-relation.command'; -import { WorkspaceEntity } 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 { WorkspaceSchemaManagerModule } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.module'; -import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module'; - -@Module({ - imports: [ - TypeOrmModule.forFeature([ - WorkspaceEntity, - FieldMetadataEntity, - ObjectMetadataEntity, - ]), - WorkspaceDataSourceModule, - WorkspaceSchemaManagerModule, - WorkspaceMetadataVersionModule, - ], - providers: [ - RemoveFavoriteViewRelationCommand, - AddPositionsToWorkflowVersionsAndWorkflowRunsCommand, - MigrateViewsToCoreCommand, - ], - exports: [ - RemoveFavoriteViewRelationCommand, - AddPositionsToWorkflowVersionsAndWorkflowRunsCommand, - MigrateViewsToCoreCommand, - ], -}) -export class V1_5_UpgradeVersionCommandModule {} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade-version-command.module.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade-version-command.module.ts index 82eea6a5ac..59a5587ca1 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade-version-command.module.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade-version-command.module.ts @@ -1,16 +1,10 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { V0_54_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/0-54/0-54-upgrade-version-command.module'; -import { V0_55_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/0-55/0-55-upgrade-version-command.module'; -import { V1_1_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-1/1-1-upgrade-version-command.module'; -import { V1_2_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-2/1-2-upgrade-version-command.module'; -import { V1_3_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-3/1-3-upgrade-version-command.module'; -import { V1_5_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-5/1-5-upgrade-version-command.module'; +import { V1_10_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-10/1-10-upgrade-version-command.module'; import { V1_6_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-6/1-6-upgrade-version-command.module'; import { V1_7_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-7/1-7-upgrade-version-command.module'; import { V1_8_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-8/1-8-upgrade-version-command.module'; -import { V1_10_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-10/1-10-upgrade-version-command.module'; import { UpgradeCommand } from 'src/database/commands/upgrade-version-command/upgrade.command'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.module'; @@ -18,12 +12,6 @@ import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/worksp @Module({ imports: [ TypeOrmModule.forFeature([WorkspaceEntity]), - V0_54_UpgradeVersionCommandModule, - V0_55_UpgradeVersionCommandModule, - V1_1_UpgradeVersionCommandModule, - V1_2_UpgradeVersionCommandModule, - V1_3_UpgradeVersionCommandModule, - V1_5_UpgradeVersionCommandModule, V1_6_UpgradeVersionCommandModule, V1_7_UpgradeVersionCommandModule, V1_8_UpgradeVersionCommandModule, diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts index 157c9309cf..ef85b6f9a3 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts @@ -9,29 +9,11 @@ import { UpgradeCommandRunner, type VersionCommands, } from 'src/database/commands/command-runners/upgrade.command-runner'; -import { CleanNotFoundFilesCommand } from 'src/database/commands/upgrade-version-command/0-54/0-54-clean-not-found-files.command'; -import { FixCreatedByDefaultValueCommand } from 'src/database/commands/upgrade-version-command/0-54/0-54-created-by-default-value.command'; -import { FixStandardSelectFieldsPositionCommand } from 'src/database/commands/upgrade-version-command/0-54/0-54-fix-standard-select-fields-position.command'; -import { LowercaseUserAndInvitationEmailsCommand } from 'src/database/commands/upgrade-version-command/0-54/0-54-lowercase-user-and-invitation-emails.command'; -import { MigrateDefaultAvatarUrlToUserWorkspaceCommand } from 'src/database/commands/upgrade-version-command/0-54/0-54-migrate-default-avatar-url-to-user-workspace.command'; -import { DeduplicateIndexedFieldsCommand } from 'src/database/commands/upgrade-version-command/0-55/0-55-deduplicate-indexed-fields.command'; -import { AddEnqueuedStatusToWorkflowRunCommand } from 'src/database/commands/upgrade-version-command/1-1/1-1-add-enqueued-status-to-workflow-run.command'; -import { FixSchemaArrayTypeCommand } from 'src/database/commands/upgrade-version-command/1-1/1-1-fix-schema-array-type.command'; -import { FixUpdateStandardFieldsIsLabelSyncedWithName } from 'src/database/commands/upgrade-version-command/1-1/1-1-fix-update-standard-field-is-label-synced-with-name.command'; -import { MigrateWorkflowRunStatesCommand } from 'src/database/commands/upgrade-version-command/1-1/1-1-migrate-workflow-run-state.command'; import { AddWorkflowRunStopStatusesCommand } from 'src/database/commands/upgrade-version-command/1-10/1-10-add-workflow-run-stop-statuses.command'; import { CleanOrphanedKanbanAggregateOperationFieldMetadataIdCommand } from 'src/database/commands/upgrade-version-command/1-10/1-10-clean-orphaned-kanban-aggregate-operation-field-metadata-id.command'; import { MigrateAttachmentAuthorToCreatedByCommand } from 'src/database/commands/upgrade-version-command/1-10/1-10-migrate-attachment-author-to-created-by.command'; import { MigrateAttachmentTypeToFileCategoryCommand } from 'src/database/commands/upgrade-version-command/1-10/1-10-migrate-attachment-type-to-file-category.command'; import { RegenerateSearchVectorsCommand } from 'src/database/commands/upgrade-version-command/1-10/1-10-regenerate-search-vectors.command'; -import { AddEnqueuedStatusToWorkflowRunV2Command } from 'src/database/commands/upgrade-version-command/1-2/1-2-add-enqueued-status-to-workflow-run-v2.command'; -import { AddNextStepIdsToWorkflowVersionTriggers } from 'src/database/commands/upgrade-version-command/1-2/1-2-add-next-step-ids-to-workflow-version-triggers.command'; -import { RemoveWorkflowRunsWithoutState } from 'src/database/commands/upgrade-version-command/1-2/1-2-remove-workflow-runs-without-state.command'; -import { AddNextStepIdsToWorkflowRunsTrigger } from 'src/database/commands/upgrade-version-command/1-3/1-3-add-next-step-ids-to-workflow-runs-trigger.command'; -import { UpdateTimestampColumnTypeInWorkspaceSchemaCommand } from 'src/database/commands/upgrade-version-command/1-3/1-3-update-timestamp-column-type-in-workspace-schema.command'; -import { AddPositionsToWorkflowVersionsAndWorkflowRunsCommand } from 'src/database/commands/upgrade-version-command/1-5/1-5-add-positions-to-workflow-versions-and-workflow-runs.command'; -import { MigrateViewsToCoreCommand } from 'src/database/commands/upgrade-version-command/1-5/1-5-migrate-views-to-core.command'; -import { RemoveFavoriteViewRelationCommand } from 'src/database/commands/upgrade-version-command/1-5/1-5-remove-favorite-view-relation.command'; import { FixLabelIdentifierPositionAndVisibilityCommand } from 'src/database/commands/upgrade-version-command/1-6/1-6-fix-label-identifier-position-and-visibility.command'; import { BackfillWorkflowManualTriggerAvailabilityCommand } from 'src/database/commands/upgrade-version-command/1-7/1-7-backfill-workflow-manual-trigger-availability.command'; import { DeduplicateUniqueFieldsCommand } from 'src/database/commands/upgrade-version-command/1-8/1-8-deduplicate-unique-fields.command'; @@ -58,36 +40,6 @@ export class UpgradeCommand extends UpgradeCommandRunner { protected readonly twentyORMGlobalManager: TwentyORMGlobalManager, protected readonly syncWorkspaceMetadataCommand: SyncWorkspaceMetadataCommand, - // 0.54 Commands - protected readonly fixStandardSelectFieldsPositionCommand: FixStandardSelectFieldsPositionCommand, - protected readonly fixCreatedByDefaultValueCommand: FixCreatedByDefaultValueCommand, - protected readonly cleanNotFoundFilesCommand: CleanNotFoundFilesCommand, - protected readonly lowercaseUserAndInvitationEmailsCommand: LowercaseUserAndInvitationEmailsCommand, - protected readonly migrateDefaultAvatarUrlToUserWorkspaceCommand: MigrateDefaultAvatarUrlToUserWorkspaceCommand, - - // 0.55 Commands - protected readonly deduplicateIndexedFieldsCommand: DeduplicateIndexedFieldsCommand, - - // 1.1 Commands - protected readonly fixSchemaArrayTypeCommand: FixSchemaArrayTypeCommand, - protected readonly fixUpdateStandardFieldsIsLabelSyncedWithNameCommand: FixUpdateStandardFieldsIsLabelSyncedWithName, - protected readonly migrateWorkflowRunStatesCommand: MigrateWorkflowRunStatesCommand, - protected readonly addEnqueuedStatusToWorkflowRunCommand: AddEnqueuedStatusToWorkflowRunCommand, - - // 1.2 Commands - protected readonly removeWorkflowRunsWithoutState: RemoveWorkflowRunsWithoutState, - protected readonly addNextStepIdsToWorkflowVersionTriggers: AddNextStepIdsToWorkflowVersionTriggers, - protected readonly addEnqueuedStatusToWorkflowRunV2Command: AddEnqueuedStatusToWorkflowRunV2Command, - - // 1.3 Commands - protected readonly addNextStepIdsToWorkflowRunsTrigger: AddNextStepIdsToWorkflowRunsTrigger, - protected readonly updateTimestampColumnTypeInWorkspaceSchemaCommand: UpdateTimestampColumnTypeInWorkspaceSchemaCommand, - - // 1.5 Commands - protected readonly removeFavoriteViewRelationCommand: RemoveFavoriteViewRelationCommand, - protected readonly addPositionsToWorkflowVersionsAndWorkflowRunsCommand: AddPositionsToWorkflowVersionsAndWorkflowRunsCommand, - protected readonly migrateViewsToCoreCommand: MigrateViewsToCoreCommand, - // 1.6 Commands protected readonly fixLabelIdentifierPositionAndVisibilityCommand: FixLabelIdentifierPositionAndVisibilityCommand, @@ -115,79 +67,6 @@ export class UpgradeCommand extends UpgradeCommandRunner { syncWorkspaceMetadataCommand, ); - const commands_053: VersionCommands = { - beforeSyncMetadata: [], - afterSyncMetadata: [], - }; - - const commands_054: VersionCommands = { - beforeSyncMetadata: [ - this.fixStandardSelectFieldsPositionCommand, - this.fixCreatedByDefaultValueCommand, - ], - afterSyncMetadata: [ - this.cleanNotFoundFilesCommand, - this.lowercaseUserAndInvitationEmailsCommand, - this.migrateDefaultAvatarUrlToUserWorkspaceCommand, - ], - }; - - const commands_055: VersionCommands = { - beforeSyncMetadata: [this.deduplicateIndexedFieldsCommand], - afterSyncMetadata: [], - }; - - const commands_060: VersionCommands = { - afterSyncMetadata: [], - beforeSyncMetadata: [], - }; - - const commands_100: VersionCommands = { - afterSyncMetadata: [], - beforeSyncMetadata: [], - }; - - const commands_110: VersionCommands = { - beforeSyncMetadata: [ - this.fixUpdateStandardFieldsIsLabelSyncedWithNameCommand, - this.fixSchemaArrayTypeCommand, - this.addEnqueuedStatusToWorkflowRunCommand, - ], - afterSyncMetadata: [this.migrateWorkflowRunStatesCommand], - }; - - const commands_120: VersionCommands = { - beforeSyncMetadata: [ - this.removeWorkflowRunsWithoutState, - this.addNextStepIdsToWorkflowVersionTriggers, - this.addEnqueuedStatusToWorkflowRunV2Command, - ], - afterSyncMetadata: [], - }; - - const commands_130: VersionCommands = { - beforeSyncMetadata: [ - this.addNextStepIdsToWorkflowVersionTriggers, // We add that command again because nextStepIds where not added on freshly created triggers. It will be done in 1.3 - this.addNextStepIdsToWorkflowRunsTrigger, - this.updateTimestampColumnTypeInWorkspaceSchemaCommand, - ], - afterSyncMetadata: [], - }; - - const commands_140: VersionCommands = { - beforeSyncMetadata: [], - afterSyncMetadata: [], - }; - - const commands_150: VersionCommands = { - beforeSyncMetadata: [ - this.migrateViewsToCoreCommand, - this.removeFavoriteViewRelationCommand, - this.addPositionsToWorkflowVersionsAndWorkflowRunsCommand, - ], - afterSyncMetadata: [], - }; - const commands_160: VersionCommands = { beforeSyncMetadata: [this.fixLabelIdentifierPositionAndVisibilityCommand], afterSyncMetadata: [], @@ -224,16 +103,6 @@ export class UpgradeCommand extends UpgradeCommandRunner { }; this.allCommands = { - '0.53.0': commands_053, - '0.54.0': commands_054, - '0.55.0': commands_055, - '0.60.0': commands_060, - '1.0.0': commands_100, - '1.1.0': commands_110, - '1.2.0': commands_120, - '1.3.0': commands_130, - '1.4.0': commands_140, - '1.5.0': commands_150, '1.6.0': commands_160, '1.7.0': commands_170, '1.8.0': commands_180, diff --git a/packages/twenty-server/src/engine/metadata-modules/view/types/view-open-record-in-type.type.ts b/packages/twenty-server/src/engine/metadata-modules/view/types/view-open-record-in-type.type.ts new file mode 100644 index 0000000000..c2a1314b59 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/view/types/view-open-record-in-type.type.ts @@ -0,0 +1,4 @@ +export enum ViewOpenRecordInType { + SIDE_PANEL = 'SIDE_PANEL', + RECORD_PAGE = 'RECORD_PAGE', +} diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-core-views.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-core-views.ts index 8e87894395..9824bf4087 100644 --- a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-core-views.ts +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-core-views.ts @@ -11,6 +11,7 @@ import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entit import { ViewKey } from 'src/engine/metadata-modules/view/enums/view-key.enum'; import { ViewOpenRecordIn } from 'src/engine/metadata-modules/view/enums/view-open-record-in'; import { ViewType } from 'src/engine/metadata-modules/view/enums/view-type.enum'; +import { ViewOpenRecordInType } from 'src/engine/metadata-modules/view/types/view-open-record-in-type.type'; import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager'; import { shouldSeedWorkspaceFavorite } from 'src/engine/utils/should-seed-workspace-favorite'; import { prefillWorkspaceFavorites } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-workspace-favorites'; @@ -27,7 +28,6 @@ import { tasksByStatusView } from 'src/engine/workspace-manager/standard-objects 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'; import { workflowsAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/workflows-all.view'; -import { ViewOpenRecordInType } from 'src/modules/view/standard-objects/view.workspace-entity'; type PrefillCoreViewsArgs = { coreDataSource: DataSource; diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/types/view-definition.interface.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/types/view-definition.interface.ts index 9330f08d8a..e5e0f42b46 100644 --- a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/types/view-definition.interface.ts +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/types/view-definition.interface.ts @@ -2,7 +2,7 @@ import { type MessageDescriptor } from '@lingui/core'; import { type ViewFilterOperand } from 'twenty-shared/types'; import { type AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant'; -import { type ViewOpenRecordInType } from 'src/modules/view/standard-objects/view.workspace-entity'; +import { type ViewOpenRecordInType } from 'src/engine/metadata-modules/view/types/view-open-record-in-type.type'; export interface ViewDefinition { id?: string; diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/dashboards-all.view.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/dashboards-all.view.ts index 72867a5337..d41a637535 100644 --- a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/dashboards-all.view.ts +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/dashboards-all.view.ts @@ -1,12 +1,12 @@ import { msg } from '@lingui/core/macro'; import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { ViewOpenRecordInType } from 'src/engine/metadata-modules/view/types/view-open-record-in-type.type'; import { BASE_OBJECT_STANDARD_FIELD_IDS, DASHBOARD_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'; -import { ViewOpenRecordInType } from 'src/modules/view/standard-objects/view.workspace-entity'; export const dashboardsAllView = ( objectMetadataItems: ObjectMetadataEntity[], diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/workflow-runs-all.view.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/workflow-runs-all.view.ts index af92da526d..5051691da4 100644 --- a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/workflow-runs-all.view.ts +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/workflow-runs-all.view.ts @@ -1,9 +1,9 @@ import { msg } from '@lingui/core/macro'; import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { ViewOpenRecordInType } from 'src/engine/metadata-modules/view/types/view-open-record-in-type.type'; import { WORKFLOW_RUN_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'; -import { ViewOpenRecordInType } from 'src/modules/view/standard-objects/view.workspace-entity'; export const workflowRunsAllView = ( objectMetadataItems: ObjectMetadataEntity[], diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/workflow-versions-all.view.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/workflow-versions-all.view.ts index 9cdf01849f..9406afbf32 100644 --- a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/workflow-versions-all.view.ts +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/workflow-versions-all.view.ts @@ -1,12 +1,12 @@ import { msg } from '@lingui/core/macro'; import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { ViewOpenRecordInType } from 'src/engine/metadata-modules/view/types/view-open-record-in-type.type'; import { BASE_OBJECT_STANDARD_FIELD_IDS, WORKFLOW_VERSION_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'; -import { ViewOpenRecordInType } from 'src/modules/view/standard-objects/view.workspace-entity'; export const workflowVersionsAllView = ( objectMetadataItems: ObjectMetadataEntity[], diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/workflows-all.view.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/workflows-all.view.ts index 9abcab3103..1c323b33d2 100644 --- a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/workflows-all.view.ts +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/workflows-all.view.ts @@ -1,12 +1,12 @@ import { msg } from '@lingui/core/macro'; import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { ViewOpenRecordInType } from 'src/engine/metadata-modules/view/types/view-open-record-in-type.type'; import { BASE_OBJECT_STANDARD_FIELD_IDS, WORKFLOW_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'; -import { ViewOpenRecordInType } from 'src/modules/view/standard-objects/view.workspace-entity'; export const workflowsAllView = ( objectMetadataItems: ObjectMetadataEntity[], diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-objects/index.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-objects/index.ts index 2dc2b51c77..5d52eebd6d 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-objects/index.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-objects/index.ts @@ -22,12 +22,6 @@ import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/perso import { TaskTargetWorkspaceEntity } from 'src/modules/task/standard-objects/task-target.workspace-entity'; import { TaskWorkspaceEntity } from 'src/modules/task/standard-objects/task.workspace-entity'; import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity'; -import { ViewFieldWorkspaceEntity } from 'src/modules/view/standard-objects/view-field.workspace-entity'; -import { ViewFilterGroupWorkspaceEntity } from 'src/modules/view/standard-objects/view-filter-group.workspace-entity'; -import { ViewFilterWorkspaceEntity } from 'src/modules/view/standard-objects/view-filter.workspace-entity'; -import { ViewGroupWorkspaceEntity } from 'src/modules/view/standard-objects/view-group.workspace-entity'; -import { ViewSortWorkspaceEntity } from 'src/modules/view/standard-objects/view-sort.workspace-entity'; -import { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity'; import { WorkflowAutomatedTriggerWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-automated-trigger.workspace-entity'; import { WorkflowRunWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-run.workspace-entity'; import { WorkflowVersionWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity'; @@ -48,12 +42,6 @@ export const standardObjectMetadataDefinitions = [ FavoriteWorkspaceEntity, FavoriteFolderWorkspaceEntity, TimelineActivityWorkspaceEntity, - ViewFieldWorkspaceEntity, - ViewGroupWorkspaceEntity, - ViewFilterWorkspaceEntity, - ViewFilterGroupWorkspaceEntity, - ViewSortWorkspaceEntity, - ViewWorkspaceEntity, WorkflowWorkspaceEntity, WorkflowVersionWorkspaceEntity, WorkflowRunWorkspaceEntity, diff --git a/packages/twenty-server/src/modules/modules.module.ts b/packages/twenty-server/src/modules/modules.module.ts index 6186e7cb14..68973d0d96 100644 --- a/packages/twenty-server/src/modules/modules.module.ts +++ b/packages/twenty-server/src/modules/modules.module.ts @@ -5,7 +5,6 @@ import { ConnectedAccountModule } from 'src/modules/connected-account/connected- import { FavoriteFolderModule } from 'src/modules/favorite-folder/favorite-folder.module'; import { FavoriteModule } from 'src/modules/favorite/favorite.module'; import { MessagingModule } from 'src/modules/messaging/messaging.module'; -import { StandardViewModule } from 'src/modules/view/standard-view.module'; import { WorkflowModule } from 'src/modules/workflow/workflow.module'; @Module({ @@ -13,7 +12,6 @@ import { WorkflowModule } from 'src/modules/workflow/workflow.module'; MessagingModule, CalendarModule, ConnectedAccountModule, - StandardViewModule, WorkflowModule, FavoriteFolderModule, FavoriteModule, diff --git a/packages/twenty-server/src/modules/view/standard-objects/view-field.workspace-entity.ts b/packages/twenty-server/src/modules/view/standard-objects/view-field.workspace-entity.ts deleted file mode 100644 index 8e7d158143..0000000000 --- a/packages/twenty-server/src/modules/view/standard-objects/view-field.workspace-entity.ts +++ /dev/null @@ -1,181 +0,0 @@ -import { registerEnumType } from '@nestjs/graphql'; - -import { msg } from '@lingui/core/macro'; -import { FieldMetadataType } from 'twenty-shared/types'; -import { Relation } from 'typeorm'; - -import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; - -import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant'; -import { RelationOnDeleteAction } from 'src/engine/metadata-modules/relation-metadata/relation-on-delete-action.type'; -import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity'; -import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator'; -import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator'; -import { WorkspaceIndex } from 'src/engine/twenty-orm/decorators/workspace-index.decorator'; -import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator'; -import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator'; -import { WorkspaceJoinColumn } from 'src/engine/twenty-orm/decorators/workspace-join-column.decorator'; -import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator'; -import { VIEW_FIELD_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; -import { STANDARD_OBJECT_ICONS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-icons'; -import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; -import { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity'; - -registerEnumType(AggregateOperations, { - name: 'AggregateOperations', -}); - -@WorkspaceEntity({ - standardId: STANDARD_OBJECT_IDS.viewField, - namePlural: 'viewFields', - labelSingular: msg`View Field`, - labelPlural: msg`View Fields`, - description: msg`(System) View Fields`, - icon: STANDARD_OBJECT_ICONS.viewField, -}) -@WorkspaceIsSystem() -@WorkspaceIndex(['fieldMetadataId', 'viewId'], { - isUnique: true, - indexWhereClause: '"deletedAt" IS NULL', -}) -export class ViewFieldWorkspaceEntity extends BaseWorkspaceEntity { - @WorkspaceField({ - standardId: VIEW_FIELD_STANDARD_FIELD_IDS.fieldMetadataId, - type: FieldMetadataType.UUID, - label: msg`Field Metadata Id`, - description: msg`View Field target field`, - icon: 'IconTag', - }) - fieldMetadataId: string; - - @WorkspaceField({ - standardId: VIEW_FIELD_STANDARD_FIELD_IDS.isVisible, - type: FieldMetadataType.BOOLEAN, - label: msg`Visible`, - description: msg`View Field visibility`, - icon: 'IconEye', - defaultValue: true, - }) - isVisible: boolean; - - @WorkspaceField({ - standardId: VIEW_FIELD_STANDARD_FIELD_IDS.size, - type: FieldMetadataType.NUMBER, - label: msg`Size`, - description: msg`View Field size`, - icon: 'IconEye', - defaultValue: 0, - }) - size: number; - - @WorkspaceField({ - standardId: VIEW_FIELD_STANDARD_FIELD_IDS.position, - type: FieldMetadataType.NUMBER, - label: msg`Position`, - description: msg`View Field position`, - icon: 'IconList', - defaultValue: 0, - }) - @WorkspaceIsSystem() - position: number; - - @WorkspaceRelation({ - standardId: VIEW_FIELD_STANDARD_FIELD_IDS.view, - type: RelationType.MANY_TO_ONE, - label: msg`View`, - description: msg`View Field related view`, - icon: 'IconLayoutCollage', - inverseSideTarget: () => ViewWorkspaceEntity, - inverseSideFieldKey: 'viewFields', - onDelete: RelationOnDeleteAction.CASCADE, - }) - view: Relation; - - @WorkspaceField({ - standardId: VIEW_FIELD_STANDARD_FIELD_IDS.aggregateOperation, - type: FieldMetadataType.SELECT, - label: msg`Aggregate operation`, - description: msg`Optional aggregate operation`, - icon: 'IconCalculator', - options: [ - { - value: AggregateOperations.AVG, - label: 'Average', - position: 0, - color: 'red', - }, - { - value: AggregateOperations.COUNT, - label: 'Count', - position: 1, - color: 'purple', - }, - { - value: AggregateOperations.MAX, - label: 'Maximum', - position: 2, - color: 'sky', - }, - { - value: AggregateOperations.MIN, - label: 'Minimum', - position: 3, - color: 'turquoise', - }, - { - value: AggregateOperations.SUM, - label: 'Sum', - position: 4, - color: 'yellow', - }, - { - value: AggregateOperations.COUNT_EMPTY, - label: 'Count empty', - position: 5, - color: 'red', - }, - { - value: AggregateOperations.COUNT_NOT_EMPTY, - label: 'Count not empty', - position: 6, - color: 'purple', - }, - { - value: AggregateOperations.COUNT_UNIQUE_VALUES, - label: 'Count unique values', - position: 7, - color: 'sky', - }, - { - value: AggregateOperations.PERCENTAGE_EMPTY, - label: 'Percent empty', - position: 8, - color: 'turquoise', - }, - { - value: AggregateOperations.PERCENTAGE_NOT_EMPTY, - label: 'Percent not empty', - position: 9, - color: 'yellow', - }, - { - value: AggregateOperations.COUNT_TRUE, - label: 'Count true', - position: 10, - color: 'red', - }, - { - value: AggregateOperations.COUNT_FALSE, - label: 'Count false', - position: 11, - color: 'purple', - }, - ], - defaultValue: null, - }) - @WorkspaceIsNullable() - aggregateOperation?: AggregateOperations | null; - - @WorkspaceJoinColumn('view') - viewId: string; -} diff --git a/packages/twenty-server/src/modules/view/standard-objects/view-filter-group.workspace-entity.ts b/packages/twenty-server/src/modules/view/standard-objects/view-filter-group.workspace-entity.ts deleted file mode 100644 index b5898a7324..0000000000 --- a/packages/twenty-server/src/modules/view/standard-objects/view-filter-group.workspace-entity.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { msg } from '@lingui/core/macro'; -import { FieldMetadataType } from 'twenty-shared/types'; -import { Relation } from 'typeorm'; - -import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; - -import { RelationOnDeleteAction } from 'src/engine/metadata-modules/relation-metadata/relation-on-delete-action.type'; -import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity'; -import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator'; -import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator'; -import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator'; -import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator'; -import { WorkspaceJoinColumn } from 'src/engine/twenty-orm/decorators/workspace-join-column.decorator'; -import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator'; -import { VIEW_FILTER_GROUP_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'; -import { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity'; - -export enum ViewFilterGroupLogicalOperator { - AND = 'AND', - OR = 'OR', - NOT = 'NOT', -} - -@WorkspaceEntity({ - standardId: STANDARD_OBJECT_IDS.viewFilterGroup, - namePlural: 'viewFilterGroups', - labelSingular: msg`View Filter Group`, - labelPlural: msg`View Filter Groups`, - description: msg`(System) View Filter Groups`, - icon: 'IconFilterBolt', -}) -@WorkspaceIsSystem() -export class ViewFilterGroupWorkspaceEntity extends BaseWorkspaceEntity { - @WorkspaceRelation({ - standardId: VIEW_FILTER_GROUP_STANDARD_FIELD_IDS.view, - type: RelationType.MANY_TO_ONE, - label: msg`View`, - description: msg`View`, - inverseSideTarget: () => ViewWorkspaceEntity, - inverseSideFieldKey: 'viewFilterGroups', - onDelete: RelationOnDeleteAction.CASCADE, - }) - view: Relation; - - @WorkspaceJoinColumn('view') - viewId: string; - - @WorkspaceField({ - standardId: VIEW_FILTER_GROUP_STANDARD_FIELD_IDS.parentViewFilterGroupId, - type: FieldMetadataType.UUID, - label: msg`Parent View Filter Group Id`, - description: msg`Parent View Filter Group`, - }) - @WorkspaceIsNullable() - parentViewFilterGroupId: string | null; - - @WorkspaceField({ - standardId: VIEW_FILTER_GROUP_STANDARD_FIELD_IDS.logicalOperator, - type: FieldMetadataType.SELECT, - label: msg`Logical Operator`, - description: msg`Logical operator for the filter group`, - options: [ - { - value: ViewFilterGroupLogicalOperator.AND, - label: 'AND', - position: 0, - color: 'blue', - }, - { - value: ViewFilterGroupLogicalOperator.OR, - label: 'OR', - position: 1, - color: 'green', - }, - { - value: ViewFilterGroupLogicalOperator.NOT, - label: 'NOT', - position: 2, - color: 'red', - }, - ], - defaultValue: `'${ViewFilterGroupLogicalOperator.NOT}'`, - }) - logicalOperator: string; - - @WorkspaceField({ - standardId: VIEW_FILTER_GROUP_STANDARD_FIELD_IDS.positionInViewFilterGroup, - type: FieldMetadataType.NUMBER, - label: msg`Position in view filter group`, - description: msg`Position in the parent view filter group`, - icon: 'IconHierarchy2', - }) - @WorkspaceIsNullable() - positionInViewFilterGroup: number | null; -} diff --git a/packages/twenty-server/src/modules/view/standard-objects/view-filter.workspace-entity.ts b/packages/twenty-server/src/modules/view/standard-objects/view-filter.workspace-entity.ts deleted file mode 100644 index 12fde00089..0000000000 --- a/packages/twenty-server/src/modules/view/standard-objects/view-filter.workspace-entity.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { msg } from '@lingui/core/macro'; -import { FieldMetadataType } from 'twenty-shared/types'; - -import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; -import { type Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface'; - -import { RelationOnDeleteAction } from 'src/engine/metadata-modules/relation-metadata/relation-on-delete-action.type'; -import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity'; -import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator'; -import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator'; -import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator'; -import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator'; -import { WorkspaceJoinColumn } from 'src/engine/twenty-orm/decorators/workspace-join-column.decorator'; -import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator'; -import { VIEW_FILTER_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; -import { STANDARD_OBJECT_ICONS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-icons'; -import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; -import { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity'; - -@WorkspaceEntity({ - standardId: STANDARD_OBJECT_IDS.viewFilter, - namePlural: 'viewFilters', - labelSingular: msg`View Filter`, - labelPlural: msg`View Filters`, - description: msg`(System) View Filters`, - icon: STANDARD_OBJECT_ICONS.viewFilter, -}) -@WorkspaceIsSystem() -export class ViewFilterWorkspaceEntity extends BaseWorkspaceEntity { - @WorkspaceField({ - standardId: VIEW_FILTER_STANDARD_FIELD_IDS.fieldMetadataId, - type: FieldMetadataType.UUID, - label: msg`Field Metadata Id`, - description: msg`View Filter target field`, - }) - fieldMetadataId: string; - - @WorkspaceField({ - standardId: VIEW_FILTER_STANDARD_FIELD_IDS.operand, - type: FieldMetadataType.TEXT, - label: msg`Operand`, - description: msg`View Filter operand`, - defaultValue: "'Contains'", - }) - operand: string; - - @WorkspaceField({ - standardId: VIEW_FILTER_STANDARD_FIELD_IDS.value, - type: FieldMetadataType.TEXT, - label: msg`Value`, - description: msg`View Filter value`, - }) - value: string; - - @WorkspaceField({ - standardId: VIEW_FILTER_STANDARD_FIELD_IDS.displayValue, - type: FieldMetadataType.TEXT, - label: msg`Display Value`, - description: msg`View Filter Display Value`, - }) - displayValue: string; - - @WorkspaceRelation({ - standardId: VIEW_FILTER_STANDARD_FIELD_IDS.view, - type: RelationType.MANY_TO_ONE, - label: msg`View`, - description: msg`View Filter related view`, - icon: 'IconLayoutCollage', - inverseSideTarget: () => ViewWorkspaceEntity, - inverseSideFieldKey: 'viewFilters', - onDelete: RelationOnDeleteAction.CASCADE, - }) - @WorkspaceIsNullable() - view: Relation | null; - - @WorkspaceJoinColumn('view') - viewId: string | null; - - @WorkspaceField({ - standardId: VIEW_FILTER_STANDARD_FIELD_IDS.viewFilterGroupId, - type: FieldMetadataType.UUID, - label: msg`View Filter Group Id`, - description: msg`View Filter Group`, - }) - @WorkspaceIsNullable() - viewFilterGroupId: string | null; - - @WorkspaceField({ - standardId: VIEW_FILTER_STANDARD_FIELD_IDS.positionInViewFilterGroup, - type: FieldMetadataType.NUMBER, - label: msg`Position in view filter group`, - description: msg`Position in the view filter group`, - icon: 'IconHierarchy2', - }) - @WorkspaceIsNullable() - positionInViewFilterGroup: number | null; - - @WorkspaceField({ - standardId: VIEW_FILTER_STANDARD_FIELD_IDS.subFieldName, - type: FieldMetadataType.TEXT, - label: msg`Sub field name`, - description: msg`Sub field name`, - icon: 'IconSubtask', - defaultValue: null, - }) - @WorkspaceIsNullable() - subFieldName: string | null; -} diff --git a/packages/twenty-server/src/modules/view/standard-objects/view-group.workspace-entity.ts b/packages/twenty-server/src/modules/view/standard-objects/view-group.workspace-entity.ts deleted file mode 100644 index 0aeefbaeb9..0000000000 --- a/packages/twenty-server/src/modules/view/standard-objects/view-group.workspace-entity.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { msg } from '@lingui/core/macro'; -import { FieldMetadataType } from 'twenty-shared/types'; - -import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; - -import { RelationOnDeleteAction } from 'src/engine/metadata-modules/relation-metadata/relation-on-delete-action.type'; -import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity'; -import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator'; -import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator'; -import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator'; -import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator'; -import { WorkspaceJoinColumn } from 'src/engine/twenty-orm/decorators/workspace-join-column.decorator'; -import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator'; -import { VIEW_GROUP_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; -import { STANDARD_OBJECT_ICONS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-icons'; -import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; -import { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity'; - -@WorkspaceEntity({ - standardId: STANDARD_OBJECT_IDS.viewGroup, - namePlural: 'viewGroups', - labelSingular: msg`View Group`, - labelPlural: msg`View Groups`, - description: msg`(System) View Groups`, - icon: STANDARD_OBJECT_ICONS.viewGroup, -}) -@WorkspaceIsSystem() -export class ViewGroupWorkspaceEntity extends BaseWorkspaceEntity { - @WorkspaceField({ - standardId: VIEW_GROUP_STANDARD_FIELD_IDS.fieldMetadataId, - type: FieldMetadataType.UUID, - label: msg`Field Metadata Id`, - description: msg`View Group target field`, - icon: 'IconTag', - }) - fieldMetadataId: string; - - @WorkspaceField({ - standardId: VIEW_GROUP_STANDARD_FIELD_IDS.isVisible, - type: FieldMetadataType.BOOLEAN, - label: msg`Visible`, - description: msg`View Group visibility`, - icon: 'IconEye', - defaultValue: true, - }) - isVisible: boolean; - - @WorkspaceField({ - standardId: VIEW_GROUP_STANDARD_FIELD_IDS.fieldValue, - type: FieldMetadataType.TEXT, - label: msg`Field Value`, - description: msg`Group by this field value`, - }) - fieldValue: string; - - @WorkspaceField({ - standardId: VIEW_GROUP_STANDARD_FIELD_IDS.position, - type: FieldMetadataType.NUMBER, - label: msg`Position`, - description: msg`View Field position`, - icon: 'IconList', - defaultValue: 0, - }) - @WorkspaceIsSystem() - position: number; - - @WorkspaceRelation({ - standardId: VIEW_GROUP_STANDARD_FIELD_IDS.view, - type: RelationType.MANY_TO_ONE, - label: msg`View`, - description: msg`View Group related view`, - icon: 'IconLayoutCollage', - inverseSideTarget: () => ViewWorkspaceEntity, - inverseSideFieldKey: 'viewGroups', - onDelete: RelationOnDeleteAction.CASCADE, - }) - @WorkspaceIsNullable() - view?: ViewWorkspaceEntity | null; - - @WorkspaceJoinColumn('view') - viewId: string | null; -} diff --git a/packages/twenty-server/src/modules/view/standard-objects/view-sort.workspace-entity.ts b/packages/twenty-server/src/modules/view/standard-objects/view-sort.workspace-entity.ts deleted file mode 100644 index 3b24e3acdc..0000000000 --- a/packages/twenty-server/src/modules/view/standard-objects/view-sort.workspace-entity.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { msg } from '@lingui/core/macro'; -import { FieldMetadataType } from 'twenty-shared/types'; - -import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; -import { type Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface'; - -import { RelationOnDeleteAction } from 'src/engine/metadata-modules/relation-metadata/relation-on-delete-action.type'; -import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity'; -import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator'; -import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator'; -import { WorkspaceIndex } from 'src/engine/twenty-orm/decorators/workspace-index.decorator'; -import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator'; -import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator'; -import { WorkspaceJoinColumn } from 'src/engine/twenty-orm/decorators/workspace-join-column.decorator'; -import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator'; -import { VIEW_SORT_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; -import { STANDARD_OBJECT_ICONS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-icons'; -import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; -import { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity'; - -@WorkspaceEntity({ - standardId: STANDARD_OBJECT_IDS.viewSort, - namePlural: 'viewSorts', - labelSingular: msg`View Sort`, - labelPlural: msg`View Sorts`, - description: msg`(System) View Sorts`, - icon: STANDARD_OBJECT_ICONS.viewSort, -}) -@WorkspaceIsSystem() -@WorkspaceIndex(['fieldMetadataId', 'viewId'], { - isUnique: true, - indexWhereClause: '"deletedAt" IS NULL', -}) -export class ViewSortWorkspaceEntity extends BaseWorkspaceEntity { - @WorkspaceField({ - standardId: VIEW_SORT_STANDARD_FIELD_IDS.fieldMetadataId, - type: FieldMetadataType.UUID, - label: msg`Field Metadata Id`, - description: msg`View Sort target field`, - icon: 'IconTag', - }) - fieldMetadataId: string; - - @WorkspaceField({ - standardId: VIEW_SORT_STANDARD_FIELD_IDS.direction, - type: FieldMetadataType.TEXT, - label: msg`Direction`, - description: msg`View Sort direction`, - defaultValue: "'asc'", - }) - direction: string; - - @WorkspaceRelation({ - standardId: VIEW_SORT_STANDARD_FIELD_IDS.view, - type: RelationType.MANY_TO_ONE, - label: msg`View`, - description: msg`View Sort related view`, - icon: 'IconLayoutCollage', - inverseSideTarget: () => ViewWorkspaceEntity, - inverseSideFieldKey: 'viewSorts', - onDelete: RelationOnDeleteAction.CASCADE, - }) - @WorkspaceIsNullable() - view: Relation | null; - - @WorkspaceJoinColumn('view') - viewId: string | null; -} diff --git a/packages/twenty-server/src/modules/view/standard-objects/view.workspace-entity.ts b/packages/twenty-server/src/modules/view/standard-objects/view.workspace-entity.ts deleted file mode 100644 index 477a5c717c..0000000000 --- a/packages/twenty-server/src/modules/view/standard-objects/view.workspace-entity.ts +++ /dev/null @@ -1,307 +0,0 @@ -import { registerEnumType } from '@nestjs/graphql'; - -import { msg } from '@lingui/core/macro'; -import { FieldMetadataType } from 'twenty-shared/types'; - -import { RelationOnDeleteAction } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-on-delete-action.interface'; -import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; -import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface'; - -import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant'; -import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity'; -import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator'; -import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator'; -import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator'; -import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator'; -import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator'; -import { VIEW_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; -import { STANDARD_OBJECT_ICONS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-icons'; -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 { ViewFilterGroupWorkspaceEntity } from 'src/modules/view/standard-objects/view-filter-group.workspace-entity'; -import { ViewFilterWorkspaceEntity } from 'src/modules/view/standard-objects/view-filter.workspace-entity'; -import { ViewGroupWorkspaceEntity } from 'src/modules/view/standard-objects/view-group.workspace-entity'; -import { ViewSortWorkspaceEntity } from 'src/modules/view/standard-objects/view-sort.workspace-entity'; - -export enum ViewOpenRecordInType { - SIDE_PANEL = 'SIDE_PANEL', - RECORD_PAGE = 'RECORD_PAGE', -} - -registerEnumType(ViewOpenRecordInType, { - name: 'ViewOpenRecordInType', -}); - -@WorkspaceEntity({ - standardId: STANDARD_OBJECT_IDS.view, - namePlural: 'views', - labelSingular: msg`View`, - labelPlural: msg`Views`, - description: msg`(System) Views`, - icon: STANDARD_OBJECT_ICONS.view, - labelIdentifierStandardId: VIEW_STANDARD_FIELD_IDS.name, -}) -@WorkspaceIsSystem() -export class ViewWorkspaceEntity extends BaseWorkspaceEntity { - @WorkspaceField({ - standardId: VIEW_STANDARD_FIELD_IDS.name, - type: FieldMetadataType.TEXT, - label: msg`Name`, - description: msg`View name`, - }) - name: string; - - @WorkspaceField({ - standardId: VIEW_STANDARD_FIELD_IDS.objectMetadataId, - type: FieldMetadataType.UUID, - label: msg`Object Metadata Id`, - description: msg`View target object`, - }) - objectMetadataId: string; - - @WorkspaceField({ - standardId: VIEW_STANDARD_FIELD_IDS.type, - type: FieldMetadataType.TEXT, - label: msg`Type`, - description: msg`View type`, - defaultValue: "'table'", - }) - type: string; - - @WorkspaceField({ - standardId: VIEW_STANDARD_FIELD_IDS.key, - type: FieldMetadataType.SELECT, - label: msg`Key`, - description: msg`View key`, - options: [{ value: 'INDEX', label: 'Index', position: 0, color: 'red' }], - defaultValue: "'INDEX'", - }) - @WorkspaceIsNullable() - key: string; - - @WorkspaceField({ - standardId: VIEW_STANDARD_FIELD_IDS.icon, - type: FieldMetadataType.TEXT, - label: msg`Icon`, - description: msg`View icon`, - }) - icon: string; - - @WorkspaceField({ - standardId: VIEW_STANDARD_FIELD_IDS.kanbanFieldMetadataId, - type: FieldMetadataType.TEXT, - label: msg`kanbanfieldMetadataId`, - description: msg`View Kanban column field`, - }) - /** - * @deprecated Use `viewGroups.fieldMetadataId` instead - */ - kanbanFieldMetadataId: string; - - @WorkspaceField({ - standardId: VIEW_STANDARD_FIELD_IDS.position, - type: FieldMetadataType.POSITION, - label: msg`Position`, - description: msg`View position`, - defaultValue: 0, - }) - @WorkspaceIsSystem() - position: number; - - @WorkspaceField({ - standardId: VIEW_STANDARD_FIELD_IDS.isCompact, - type: FieldMetadataType.BOOLEAN, - label: msg`Compact View`, - description: msg`Describes if the view is in compact mode`, - defaultValue: false, - }) - isCompact: boolean; - - @WorkspaceField({ - standardId: VIEW_STANDARD_FIELD_IDS.openRecordIn, - type: FieldMetadataType.SELECT, - label: msg`Open Record In`, - description: msg`Display the records in a side panel or in a record page`, - defaultValue: `'${ViewOpenRecordInType.SIDE_PANEL}'`, - options: [ - { - value: ViewOpenRecordInType.SIDE_PANEL, - label: 'Side Panel', - position: 0, - color: 'green', - }, - { - value: ViewOpenRecordInType.RECORD_PAGE, - label: 'Record Page', - position: 1, - color: 'blue', - }, - ], - }) - openRecordIn: ViewOpenRecordInType; - - @WorkspaceRelation({ - standardId: VIEW_STANDARD_FIELD_IDS.viewFields, - type: RelationType.ONE_TO_MANY, - label: msg`View Fields`, - description: msg`View Fields`, - icon: 'IconTag', - inverseSideTarget: () => ViewFieldWorkspaceEntity, - onDelete: RelationOnDeleteAction.CASCADE, - }) - @WorkspaceIsNullable() - viewFields: Relation; - - @WorkspaceRelation({ - standardId: VIEW_STANDARD_FIELD_IDS.viewGroups, - type: RelationType.ONE_TO_MANY, - label: msg`View Groups`, - description: msg`View Groups`, - icon: 'IconTag', - inverseSideTarget: () => ViewGroupWorkspaceEntity, - onDelete: RelationOnDeleteAction.SET_NULL, - }) - @WorkspaceIsNullable() - viewGroups: Relation; - - @WorkspaceRelation({ - standardId: VIEW_STANDARD_FIELD_IDS.viewFilters, - type: RelationType.ONE_TO_MANY, - label: msg`View Filters`, - description: msg`View Filters`, - icon: 'IconFilterBolt', - inverseSideTarget: () => ViewFilterWorkspaceEntity, - onDelete: RelationOnDeleteAction.SET_NULL, - }) - @WorkspaceIsNullable() - viewFilters: Relation; - - @WorkspaceRelation({ - standardId: VIEW_STANDARD_FIELD_IDS.viewFilterGroups, - type: RelationType.ONE_TO_MANY, - label: msg`View Filter Groups`, - description: msg`View Filter Groups`, - icon: 'IconFilterBolt', - inverseSideTarget: () => ViewFilterGroupWorkspaceEntity, - onDelete: RelationOnDeleteAction.SET_NULL, - }) - @WorkspaceIsNullable() - viewFilterGroups: Relation; - - @WorkspaceRelation({ - standardId: VIEW_STANDARD_FIELD_IDS.viewSorts, - type: RelationType.ONE_TO_MANY, - label: msg`View Sorts`, - description: msg`View Sorts`, - icon: 'IconArrowsSort', - inverseSideTarget: () => ViewSortWorkspaceEntity, - onDelete: RelationOnDeleteAction.SET_NULL, - }) - @WorkspaceIsNullable() - viewSorts: Relation; - - @WorkspaceField({ - standardId: VIEW_STANDARD_FIELD_IDS.kanbanAggregateOperation, - type: FieldMetadataType.SELECT, - label: msg`Aggregate operation`, - description: msg`Optional aggregate operation`, - icon: 'IconCalculator', - options: [ - { - value: AggregateOperations.AVG, - label: 'Average', - position: 0, - color: 'red', - }, - { - value: AggregateOperations.COUNT, - label: 'Count', - position: 1, - color: 'purple', - }, - { - value: AggregateOperations.MAX, - label: 'Maximum', - position: 2, - color: 'sky', - }, - { - value: AggregateOperations.MIN, - label: 'Minimum', - position: 3, - color: 'turquoise', - }, - { - value: AggregateOperations.SUM, - label: 'Sum', - position: 4, - color: 'yellow', - }, - { - value: AggregateOperations.COUNT_EMPTY, - label: 'Count empty', - position: 5, - color: 'red', - }, - { - value: AggregateOperations.COUNT_NOT_EMPTY, - label: 'Count not empty', - position: 6, - color: 'purple', - }, - { - value: AggregateOperations.COUNT_UNIQUE_VALUES, - label: 'Count unique values', - position: 7, - color: 'sky', - }, - { - value: AggregateOperations.PERCENTAGE_EMPTY, - label: 'Percent empty', - position: 8, - color: 'turquoise', - }, - { - value: AggregateOperations.PERCENTAGE_NOT_EMPTY, - label: 'Percent not empty', - position: 9, - color: 'yellow', - }, - { - value: AggregateOperations.COUNT_TRUE, - label: 'Count true', - position: 10, - color: 'red', - }, - { - value: AggregateOperations.COUNT_FALSE, - label: 'Count false', - position: 11, - color: 'purple', - }, - ], - defaultValue: `'${AggregateOperations.COUNT}'`, - }) - @WorkspaceIsNullable() - kanbanAggregateOperation?: AggregateOperations | null; - - @WorkspaceField({ - standardId: VIEW_STANDARD_FIELD_IDS.kanbanAggregateOperationFieldMetadataId, - type: FieldMetadataType.UUID, - label: msg`Field metadata used for aggregate operation`, - description: msg`Field metadata used for aggregate operation`, - defaultValue: null, - }) - @WorkspaceIsNullable() - kanbanAggregateOperationFieldMetadataId?: string | null; - - @WorkspaceField({ - standardId: VIEW_STANDARD_FIELD_IDS.anyFieldFilterValue, - type: FieldMetadataType.TEXT, - label: msg`Any field filter value`, - description: msg`Any field filter value`, - defaultValue: null, - }) - @WorkspaceIsNullable() - anyFieldFilterValue?: string | null; -} diff --git a/packages/twenty-server/src/modules/view/standard-view.module.ts b/packages/twenty-server/src/modules/view/standard-view.module.ts deleted file mode 100644 index 9e32ab607d..0000000000 --- a/packages/twenty-server/src/modules/view/standard-view.module.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; - -import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -import { ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity'; -import { ViewFilterGroupEntity } from 'src/engine/metadata-modules/view-filter-group/entities/view-filter-group.entity'; -import { ViewFilterEntity } from 'src/engine/metadata-modules/view-filter/entities/view-filter.entity'; -import { ViewGroupEntity } from 'src/engine/metadata-modules/view-group/entities/view-group.entity'; -import { ViewSortEntity } from 'src/engine/metadata-modules/view-sort/entities/view-sort.entity'; -import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity'; - -@Module({ - imports: [ - TypeOrmModule.forFeature([ - ViewEntity, - ViewFieldEntity, - ViewFilterEntity, - ViewFilterGroupEntity, - ViewGroupEntity, - ViewSortEntity, - ]), - TypeOrmModule.forFeature([ObjectMetadataEntity]), - FeatureFlagModule, - ], - - providers: [], - exports: [], -}) -export class StandardViewModule {} diff --git a/packages/twenty-server/src/modules/view/utils/convert-view-filter-workspace-value-to-core-value.ts b/packages/twenty-server/src/modules/view/utils/convert-view-filter-workspace-value-to-core-value.ts deleted file mode 100644 index 44900e63e9..0000000000 --- a/packages/twenty-server/src/modules/view/utils/convert-view-filter-workspace-value-to-core-value.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { type ViewFilterValue } from 'src/engine/metadata-modules/view-filter/types/view-filter-value.type'; - -export const convertViewFilterWorkspaceValueToCoreValue = ( - value: string, -): ViewFilterValue => { - try { - return JSON.parse(value); - } catch { - return value; - } -}; diff --git a/packages/twenty-server/src/modules/view/views.exception.ts b/packages/twenty-server/src/modules/view/views.exception.ts deleted file mode 100644 index 865b47bd00..0000000000 --- a/packages/twenty-server/src/modules/view/views.exception.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { CustomException } from 'src/utils/custom-exception'; - -export class ViewException extends CustomException {} - -export enum ViewExceptionCode { - CORE_VIEW_SYNC_ERROR = 'CORE_VIEW_SYNC_ERROR', -} diff --git a/packages/twenty-server/test/integration/graphql/suites/group-by-resolver.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/group-by-resolver.integration-spec.ts index 0d176af3a4..6850286756 100644 --- a/packages/twenty-server/test/integration/graphql/suites/group-by-resolver.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/group-by-resolver.integration-spec.ts @@ -14,7 +14,7 @@ import { ViewFilterOperand } from 'twenty-shared/types'; import { type FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto'; import { type ObjectMetadataDTO } from 'src/engine/metadata-modules/object-metadata/dtos/object-metadata.dto'; -import { ViewFilterGroupLogicalOperator } from 'src/modules/view/standard-objects/view-filter-group.workspace-entity'; +import { ViewFilterGroupLogicalOperator } from 'src/engine/metadata-modules/view-filter-group/enums/view-filter-group-logical-operator'; describe('group-by resolver (integration)', () => { describe('standard case', () => { diff --git a/packages/twenty-server/test/integration/graphql/suites/object-generated/view-fields.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/object-generated/view-fields.integration-spec.ts deleted file mode 100644 index 56f3c9783f..0000000000 --- a/packages/twenty-server/test/integration/graphql/suites/object-generated/view-fields.integration-spec.ts +++ /dev/null @@ -1,61 +0,0 @@ -import request from 'supertest'; - -const client = request(`http://localhost:${APP_PORT}`); - -describe('viewFieldsResolver (e2e)', () => { - it('should find many viewFields', () => { - const queryData = { - query: ` - query viewFields { - viewFields { - edges { - node { - fieldMetadataId - isVisible - size - position - id - createdAt - updatedAt - deletedAt - viewId - } - } - } - } - `, - }; - - return client - .post('/graphql') - .set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`) - .send(queryData) - .expect(200) - .expect((res) => { - expect(res.body.data).toBeDefined(); - expect(res.body.errors).toBeUndefined(); - }) - .expect((res) => { - const data = res.body.data.viewFields; - - expect(data).toBeDefined(); - expect(Array.isArray(data.edges)).toBe(true); - - const edges = data.edges; - - if (edges.length > 0) { - const viewFields = edges[0].node; - - expect(viewFields).toHaveProperty('fieldMetadataId'); - expect(viewFields).toHaveProperty('isVisible'); - expect(viewFields).toHaveProperty('size'); - expect(viewFields).toHaveProperty('position'); - expect(viewFields).toHaveProperty('id'); - expect(viewFields).toHaveProperty('createdAt'); - expect(viewFields).toHaveProperty('updatedAt'); - expect(viewFields).toHaveProperty('deletedAt'); - expect(viewFields).toHaveProperty('viewId'); - } - }); - }); -}); diff --git a/packages/twenty-server/test/integration/graphql/suites/object-generated/view-filters.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/object-generated/view-filters.integration-spec.ts deleted file mode 100644 index ad58438c06..0000000000 --- a/packages/twenty-server/test/integration/graphql/suites/object-generated/view-filters.integration-spec.ts +++ /dev/null @@ -1,61 +0,0 @@ -import request from 'supertest'; - -const client = request(`http://localhost:${APP_PORT}`); - -describe('viewFiltersResolver (e2e)', () => { - it('should find many viewFilters', () => { - const queryData = { - query: ` - query viewFilters { - viewFilters { - edges { - node { - fieldMetadataId - operand - value - displayValue - id - createdAt - updatedAt - deletedAt - viewId - } - } - } - } - `, - }; - - return client - .post('/graphql') - .set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`) - .send(queryData) - .expect(200) - .expect((res) => { - expect(res.body.data).toBeDefined(); - expect(res.body.errors).toBeUndefined(); - }) - .expect((res) => { - const data = res.body.data.viewFilters; - - expect(data).toBeDefined(); - expect(Array.isArray(data.edges)).toBe(true); - - const edges = data.edges; - - if (edges.length > 0) { - const viewFilters = edges[0].node; - - expect(viewFilters).toHaveProperty('fieldMetadataId'); - expect(viewFilters).toHaveProperty('operand'); - expect(viewFilters).toHaveProperty('value'); - expect(viewFilters).toHaveProperty('displayValue'); - expect(viewFilters).toHaveProperty('id'); - expect(viewFilters).toHaveProperty('createdAt'); - expect(viewFilters).toHaveProperty('updatedAt'); - expect(viewFilters).toHaveProperty('deletedAt'); - expect(viewFilters).toHaveProperty('viewId'); - } - }); - }); -}); diff --git a/packages/twenty-server/test/integration/graphql/suites/object-generated/view-sorts.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/object-generated/view-sorts.integration-spec.ts deleted file mode 100644 index 2aa830abd5..0000000000 --- a/packages/twenty-server/test/integration/graphql/suites/object-generated/view-sorts.integration-spec.ts +++ /dev/null @@ -1,57 +0,0 @@ -import request from 'supertest'; - -const client = request(`http://localhost:${APP_PORT}`); - -describe('viewSortsResolver (e2e)', () => { - it('should find many viewSorts', () => { - const queryData = { - query: ` - query viewSorts { - viewSorts { - edges { - node { - fieldMetadataId - direction - id - createdAt - updatedAt - deletedAt - viewId - } - } - } - } - `, - }; - - return client - .post('/graphql') - .set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`) - .send(queryData) - .expect(200) - .expect((res) => { - expect(res.body.data).toBeDefined(); - expect(res.body.errors).toBeUndefined(); - }) - .expect((res) => { - const data = res.body.data.viewSorts; - - expect(data).toBeDefined(); - expect(Array.isArray(data.edges)).toBe(true); - - const edges = data.edges; - - if (edges.length > 0) { - const viewSorts = edges[0].node; - - expect(viewSorts).toHaveProperty('fieldMetadataId'); - expect(viewSorts).toHaveProperty('direction'); - expect(viewSorts).toHaveProperty('id'); - expect(viewSorts).toHaveProperty('createdAt'); - expect(viewSorts).toHaveProperty('updatedAt'); - expect(viewSorts).toHaveProperty('deletedAt'); - expect(viewSorts).toHaveProperty('viewId'); - } - }); - }); -}); diff --git a/packages/twenty-server/test/integration/graphql/suites/object-generated/views.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/object-generated/views.integration-spec.ts deleted file mode 100644 index 689134b0cf..0000000000 --- a/packages/twenty-server/test/integration/graphql/suites/object-generated/views.integration-spec.ts +++ /dev/null @@ -1,67 +0,0 @@ -import request from 'supertest'; - -const client = request(`http://localhost:${APP_PORT}`); - -describe('viewsResolver (e2e)', () => { - it('should find many views', () => { - const queryData = { - query: ` - query views { - views { - edges { - node { - name - objectMetadataId - type - key - icon - kanbanFieldMetadataId - position - isCompact - id - createdAt - updatedAt - deletedAt - } - } - } - } - `, - }; - - return client - .post('/graphql') - .set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`) - .send(queryData) - .expect(200) - .expect((res) => { - expect(res.body.data).toBeDefined(); - expect(res.body.errors).toBeUndefined(); - }) - .expect((res) => { - const data = res.body.data.views; - - expect(data).toBeDefined(); - expect(Array.isArray(data.edges)).toBe(true); - - const edges = data.edges; - - if (edges.length > 0) { - const views = edges[0].node; - - expect(views).toHaveProperty('name'); - expect(views).toHaveProperty('objectMetadataId'); - expect(views).toHaveProperty('type'); - expect(views).toHaveProperty('key'); - expect(views).toHaveProperty('icon'); - expect(views).toHaveProperty('kanbanFieldMetadataId'); - expect(views).toHaveProperty('position'); - expect(views).toHaveProperty('isCompact'); - expect(views).toHaveProperty('id'); - expect(views).toHaveProperty('createdAt'); - expect(views).toHaveProperty('updatedAt'); - expect(views).toHaveProperty('deletedAt'); - } - }); - }); -}); diff --git a/packages/twenty-server/test/integration/graphql/suites/view/__snapshots__/successful-find-view.integration-spec.ts.snap b/packages/twenty-server/test/integration/graphql/suites/view/__snapshots__/successful-find-view.integration-spec.ts.snap new file mode 100644 index 0000000000..20b919b7f8 --- /dev/null +++ b/packages/twenty-server/test/integration/graphql/suites/view/__snapshots__/successful-find-view.integration-spec.ts.snap @@ -0,0 +1,114 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`successful find view with all sub-relations (e2e) Company View Structure Validation should successfully fetch complete view structure with all sub-relations in single GraphQL query 1`] = ` +{ + "anyFieldFilterValue": null, + "createdAt": Any, + "deletedAt": null, + "icon": "IconList", + "id": Any, + "isCompact": false, + "key": "INDEX", + "name": "All Companies", + "objectMetadataId": Any, + "openRecordIn": "SIDE_PANEL", + "position": 0, + "type": "TABLE", + "updatedAt": Any, + "viewFields": [ + { + "createdAt": Any, + "deletedAt": null, + "fieldMetadataId": Any, + "id": Any, + "isVisible": true, + "position": 0, + "size": 180, + "updatedAt": Any, + "viewId": Any, + }, + { + "createdAt": Any, + "deletedAt": null, + "fieldMetadataId": Any, + "id": Any, + "isVisible": true, + "position": 1, + "size": 100, + "updatedAt": Any, + "viewId": Any, + }, + { + "createdAt": Any, + "deletedAt": null, + "fieldMetadataId": Any, + "id": Any, + "isVisible": true, + "position": 2, + "size": 150, + "updatedAt": Any, + "viewId": Any, + }, + { + "createdAt": Any, + "deletedAt": null, + "fieldMetadataId": Any, + "id": Any, + "isVisible": true, + "position": 3, + "size": 150, + "updatedAt": Any, + "viewId": Any, + }, + { + "createdAt": Any, + "deletedAt": null, + "fieldMetadataId": Any, + "id": Any, + "isVisible": true, + "position": 4, + "size": 150, + "updatedAt": Any, + "viewId": Any, + }, + { + "createdAt": Any, + "deletedAt": null, + "fieldMetadataId": Any, + "id": Any, + "isVisible": true, + "position": 5, + "size": 150, + "updatedAt": Any, + "viewId": Any, + }, + { + "createdAt": Any, + "deletedAt": null, + "fieldMetadataId": Any, + "id": Any, + "isVisible": true, + "position": 6, + "size": 170, + "updatedAt": Any, + "viewId": Any, + }, + { + "createdAt": Any, + "deletedAt": null, + "fieldMetadataId": Any, + "id": Any, + "isVisible": true, + "position": 7, + "size": 170, + "updatedAt": Any, + "viewId": Any, + }, + ], + "viewFilterGroups": [], + "viewFilters": [], + "viewGroups": [], + "viewSorts": [], + "workspaceId": Any, +} +`; diff --git a/packages/twenty-server/test/integration/graphql/suites/view/successful-find-view.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/view/successful-find-view.integration-spec.ts new file mode 100644 index 0000000000..6300960991 --- /dev/null +++ b/packages/twenty-server/test/integration/graphql/suites/view/successful-find-view.integration-spec.ts @@ -0,0 +1,78 @@ +import { + VIEW_FIELD_GQL_FIELDS, + VIEW_FILTER_GQL_FIELDS, + VIEW_FILTER_GROUP_GQL_FIELDS, + VIEW_GQL_FIELDS, + VIEW_GROUP_GQL_FIELDS, + VIEW_SORT_GQL_FIELDS, +} from 'test/integration/constants/view-gql-fields.constants'; +import { findManyObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/find-many-object-metadata.util'; +import { findCoreViews } from 'test/integration/metadata/suites/view/utils/find-core-views.util'; +import { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any'; +import { jestExpectToBeDefined } from 'test/utils/jest-expect-to-be-defined.util.test'; + +import { ViewKey } from 'src/engine/metadata-modules/view/enums/view-key.enum'; + +// TODO prastoin deprecate cleanupViewRecords that breaks this test suite +describe.skip('successful find view with all sub-relations (e2e)', () => { + let companyObjectMetadataId: string; + + const COMPREHENSIVE_VIEW_GQL_FIELDS = ` + ${VIEW_GQL_FIELDS} + viewFields { + ${VIEW_FIELD_GQL_FIELDS} + } + viewFilters { + ${VIEW_FILTER_GQL_FIELDS} + } + viewGroups { + ${VIEW_GROUP_GQL_FIELDS} + } + viewSorts { + ${VIEW_SORT_GQL_FIELDS} + } + viewFilterGroups { + ${VIEW_FILTER_GROUP_GQL_FIELDS} + } + `; + + beforeAll(async () => { + const { objects } = await findManyObjectMetadata({ + expectToFail: false, + input: { + filter: {}, + paging: { + first: 1000, + }, + }, + gqlFields: ` + id + nameSingular + `, + }); + + const companyObject = objects.find((obj) => obj.nameSingular === 'company'); + + jestExpectToBeDefined(companyObject); + companyObjectMetadataId = companyObject.id; + }); + + describe('Company View Structure Validation', () => { + it('should successfully fetch complete view structure with all sub-relations in single GraphQL query', async () => { + const { data: viewsData } = await findCoreViews({ + objectMetadataId: companyObjectMetadataId, + gqlFields: COMPREHENSIVE_VIEW_GQL_FIELDS, + expectToFail: false, + }); + const testView = viewsData.getCoreViews.find( + (view) => view.key === ViewKey.INDEX, + ); + + jestExpectToBeDefined(testView); + + expect(testView).toMatchSnapshot( + extractRecordIdsAndDatesAsExpectAny({ ...testView }), + ); + }); + }); +}); diff --git a/packages/twenty-server/test/integration/utils/view-test.util.ts b/packages/twenty-server/test/integration/utils/view-test.util.ts index b6d7b7a756..f002364c2f 100644 --- a/packages/twenty-server/test/integration/utils/view-test.util.ts +++ b/packages/twenty-server/test/integration/utils/view-test.util.ts @@ -1,10 +1,10 @@ import { type ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity'; import { type ViewFilterGroupEntity } from 'src/engine/metadata-modules/view-filter-group/entities/view-filter-group.entity'; +import { ViewFilterGroupLogicalOperator } from 'src/engine/metadata-modules/view-filter-group/enums/view-filter-group-logical-operator'; import { type ViewFilterEntity } from 'src/engine/metadata-modules/view-filter/entities/view-filter.entity'; import { type ViewGroupEntity } from 'src/engine/metadata-modules/view-group/entities/view-group.entity'; import { type ViewSortEntity } from 'src/engine/metadata-modules/view-sort/entities/view-sort.entity'; import { type ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity'; -import { ViewFilterGroupLogicalOperator } from 'src/modules/view/standard-objects/view-filter-group.workspace-entity'; export const cleanupViewRecords = async (): Promise => { await global.testDataSource.query(`DELETE from "core"."view"`);