Deprecate old relations completely (#12482)

# What

Fully deprecate old relations because we have one bug tied to it and it
make the codebase complex

# How I've made this PR:
1. remove metadata datasource (we only keep 'core') => this was causing
extra complexity in the refactor + flaky reset
2. merge dev and demo datasets => as I needed to update the tests which
is very painful, I don't want to do it twice
3. remove all code tied to RELATION_METADATA /
relation-metadata.resolver, or anything tied to the old relation system
4. Remove ONE_TO_ONE and MANY_TO_MANY that are not supported
5. fix impacts on the different areas : see functional testing below 

# Functional testing

## Functional testing from the front-end:
1. Database Reset 
2. Sign In 
3. Workspace sign-up 
5. Browsing table / kanban / show 
6. Assigning a record in a one to many / in a many to one 
7. Deleting a record involved in a relation  => broken but not tied to
this PR
8. "Add new" from relation picker  => broken but not tied to this PR
9. Creating a Task / Note, Updating a Task / Note relations, Deleting a
Task / Note (from table, show page, right drawer)  => broken but not
tied to this PR
10. creating a relation from settings (custom / standard x oneToMany /
manyToOne) 
11. updating a relation from settings should not be possible 
12. deleting a relation from settings (custom / standard x oneToMany /
manyToOne) 
13. Make sure timeline activity still work (relation were involved
there), espacially with Task / Note => to be double checked  => Cannot
convert undefined or null to object
14. Workspace deletion / User deletion  
15. CSV Import should keep working  
16. Permissions: I have tested without permissions V2 as it's still hard
to test v2 work and it's not in prod yet 
17. Workflows global test  

## From the API:
1. Review open-api documentation (REST)  
2. Make sure REST Api are still able to fetch relations ==> won't do, we
have a coupling Get/Update/Create there, this requires refactoring
3. Make sure REST Api is still able to update / remove relation => won't
do same

## Automated tests
1. lint + typescript 
2. front unit tests: 
3. server unit tests 2 
4. front stories: 
5. server integration: 
6. chromatic check : expected 0
7. e2e check : expected no more that current failures

## Remove // Todos
1. All are captured by functional tests above, nothing additional to do

## (Un)related regressions
1. Table loading state is not working anymore, we see the empty state
before table content
2. Filtering by Creator Tim Ap return empty results
3. Not possible to add Tasks / Notes / Files from show page

# Result

## New seeds that can be easily extended
<img width="1920" alt="image"
src="https://github.com/user-attachments/assets/d290d130-2a5f-44e6-b419-7e42a89eec4b"
/>

## -5k lines of code
## No more 'metadata' dataSource (we only have 'core)
## No more relationMetadata (I haven't drop the table yet it's not
referenced in the code anymore)
## We are ready to fix the 6 months lag between current API results and
our mocked tests
## No more bug on relation creation / deletion

---------

Co-authored-by: Weiko <corentin@twenty.com>
Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
Charles Bochet
2025-06-10 16:45:27 +02:00
committed by GitHub
parent 264861e020
commit a68895189c
426 changed files with 48870 additions and 54125 deletions
@@ -1,232 +0,0 @@
import { InjectRepository } from '@nestjs/typeorm';
import chalk from 'chalk';
import { Command } from 'nest-commander';
import { Repository } from 'typeorm';
import { isDefined } from 'twenty-shared/utils';
import {
ActiveOrSuspendedWorkspacesMigrationCommandOptions,
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
RunOnWorkspaceArgs,
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { ADMIN_ROLE_LABEL } from 'src/engine/metadata-modules/permissions/constants/admin-role-label.constants';
import { MEMBER_ROLE_LABEL } from 'src/engine/metadata-modules/permissions/constants/member-role-label.constants';
import { RoleService } from 'src/engine/metadata-modules/role/role.service';
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
@Command({
name: 'upgrade:0-44:initialize-permissions',
description: 'Initialize permissions',
})
export class InitializePermissionsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
constructor(
@InjectRepository(Workspace, 'core')
protected readonly workspaceRepository: Repository<Workspace>,
@InjectRepository(UserWorkspace, 'core')
protected readonly userWorkspaceRepository: Repository<UserWorkspace>,
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
private readonly roleService: RoleService,
private readonly userRoleService: UserRoleService,
) {
super(workspaceRepository, twentyORMGlobalManager);
}
override async runOnWorkspace({
index,
total,
workspaceId,
options,
}: RunOnWorkspaceArgs): Promise<void> {
try {
this.logger.log(
`Running command for workspace ${workspaceId} ${index + 1}/${total}`,
);
let adminRoleId: string | undefined;
const workspaceRoles =
await this.roleService.getWorkspaceRoles(workspaceId);
adminRoleId = workspaceRoles.find(
(role) => role.label === ADMIN_ROLE_LABEL,
)?.id;
if (!isDefined(adminRoleId)) {
adminRoleId = await this.createAdminRole({
workspaceId,
options,
});
}
await this.assignAdminRoleToMembers({
workspaceId,
adminRoleId,
options,
});
await this.setAdminRoleAsDefaultRole({
workspaceId,
adminRoleId,
options,
});
const memberRole = workspaceRoles.find(
(role) => role.label === MEMBER_ROLE_LABEL,
);
if (!isDefined(memberRole)) {
await this.createMemberRole({
workspaceId,
options,
});
}
} catch (error) {
this.logger.log(
chalk.red(`Error in workspace ${workspaceId} - ${error.message}`),
);
}
}
private async createAdminRole({
workspaceId,
options,
}: {
workspaceId: string;
options: ActiveOrSuspendedWorkspacesMigrationCommandOptions;
}) {
this.logger.log(
chalk.green(`Creating admin role ${options.dryRun ? '(dry run)' : ''}`),
);
if (options.dryRun) {
return '';
}
const adminRole = await this.roleService.createAdminRole({
workspaceId,
});
return adminRole.id;
}
private async createMemberRole({
workspaceId,
options,
}: {
workspaceId: string;
options: ActiveOrSuspendedWorkspacesMigrationCommandOptions;
}) {
this.logger.log(
chalk.green(`Creating member role ${options.dryRun ? '(dry run)' : ''}`),
);
if (options.dryRun) {
return '';
}
const memberRole = await this.roleService.createMemberRole({
workspaceId,
});
return memberRole.id;
}
private async setAdminRoleAsDefaultRole({
workspaceId,
adminRoleId,
options,
}: {
workspaceId: string;
adminRoleId: string;
options: ActiveOrSuspendedWorkspacesMigrationCommandOptions;
}) {
const workspaceDefaultRole = await this.workspaceRepository.findOne({
where: {
id: workspaceId,
},
});
if (isDefined(workspaceDefaultRole?.defaultRoleId)) {
this.logger.log(
chalk.green(
'Workspace already has a default role. Skipping setting admin role as default role',
),
);
return;
}
this.logger.log(
chalk.green(
`Setting admin role as default role ${options.dryRun ? '(dry run)' : ''}`,
),
);
if (options.dryRun) {
return;
}
await this.workspaceRepository.update(workspaceId, {
defaultRoleId: adminRoleId,
});
}
private async assignAdminRoleToMembers({
workspaceId,
adminRoleId,
options,
}: {
workspaceId: string;
adminRoleId: string;
options: ActiveOrSuspendedWorkspacesMigrationCommandOptions;
}) {
const userWorkspaces = await this.userWorkspaceRepository.find({
where: {
workspaceId,
},
});
const rolesByUserWorkspace =
await this.userRoleService.getRolesByUserWorkspaces({
userWorkspaceIds: userWorkspaces.map(
(userWorkspace) => userWorkspace.id,
),
workspaceId,
});
for (const userWorkspace of userWorkspaces) {
if (
rolesByUserWorkspace
.get(userWorkspace.id)
?.some((role) => isDefined(role))
) {
this.logger.log(
chalk.green(
`User workspace ${userWorkspace.id} already has a role. Skipping role assignation`,
),
);
continue;
}
this.logger.log(
chalk.green(
`Assigning admin role to workspace member ${userWorkspace.id} ${options.dryRun ? '(dry run)' : ''}`,
),
);
if (options.dryRun) {
continue;
}
await this.userRoleService.assignRoleToUserWorkspace({
roleId: adminRoleId,
userWorkspaceId: userWorkspace.id,
workspaceId,
});
}
}
}
@@ -1,272 +0,0 @@
import { InjectRepository } from '@nestjs/typeorm';
import chalk from 'chalk';
import { Command } from 'nest-commander';
import { Repository } from 'typeorm';
import {
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
RunOnWorkspaceArgs,
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service';
import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util';
import {
WorkspaceMigrationColumnActionType,
WorkspaceMigrationTableAction,
WorkspaceMigrationTableActionType,
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
import { WorkspaceMigrationFactory } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.factory';
import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.service';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service';
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
const AGGREGATE_OPERATION_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',
},
];
@Command({
name: 'upgrade:0-44:update-view-aggregate-operations',
description:
'Update View and ViewField entities with new aggregate operations (countTrue, countFalse)',
})
export class UpdateViewAggregateOperationsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
constructor(
@InjectRepository(Workspace, 'core')
protected readonly workspaceRepository: Repository<Workspace>,
@InjectRepository(ObjectMetadataEntity, 'metadata')
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
@InjectRepository(FieldMetadataEntity, 'metadata')
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
private readonly workspaceMigrationService: WorkspaceMigrationService,
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
private readonly workspaceMigrationFactory: WorkspaceMigrationFactory,
) {
super(workspaceRepository, twentyORMGlobalManager);
}
override async runOnWorkspace({
index,
total,
workspaceId,
}: RunOnWorkspaceArgs): Promise<void> {
this.logger.log(
`Running command for workspace ${workspaceId} ${index + 1}/${total}`,
);
await this.updateViewAggregateOperations(workspaceId);
await this.updateViewFieldAggregateOperations(workspaceId);
await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations(
workspaceId,
);
await this.workspaceMetadataVersionService.incrementMetadataVersion(
workspaceId,
);
this.logger.log(
chalk.green(`Command completed for workspace ${workspaceId}.`),
);
}
private async updateViewAggregateOperations(
workspaceId: string,
): Promise<void> {
const viewObjectMetadata = await this.objectMetadataRepository.findOne({
where: {
workspaceId,
standardId: STANDARD_OBJECT_IDS.view,
},
relations: ['fields'],
});
if (!viewObjectMetadata) {
this.logger.warn(
`View object metadata not found for workspace ${workspaceId}`,
);
return;
}
const kanbanAggregateOperationField = viewObjectMetadata.fields.find(
(field) => field.name === 'kanbanAggregateOperation',
);
if (!kanbanAggregateOperationField) {
this.logger.warn(
`kanbanAggregateOperation field not found for workspace ${workspaceId}`,
);
return;
}
await this.fieldMetadataRepository.update(
{ id: kanbanAggregateOperationField.id },
{ options: AGGREGATE_OPERATION_OPTIONS },
);
this.logger.log(
`Updated kanbanAggregateOperation options for workspace ${workspaceId}`,
);
await this.workspaceMigrationService.createCustomMigration(
generateMigrationName(`update-view-operations`),
workspaceId,
[
{
name: computeObjectTargetTable(viewObjectMetadata),
action: WorkspaceMigrationTableActionType.ALTER,
columns: this.workspaceMigrationFactory.createColumnActions(
WorkspaceMigrationColumnActionType.ALTER,
{ ...kanbanAggregateOperationField, options: undefined },
{
...kanbanAggregateOperationField,
options: AGGREGATE_OPERATION_OPTIONS,
},
),
} satisfies WorkspaceMigrationTableAction,
],
);
}
private async updateViewFieldAggregateOperations(
workspaceId: string,
): Promise<void> {
const viewFieldObjectMetadata = await this.objectMetadataRepository.findOne(
{
where: {
workspaceId,
standardId: STANDARD_OBJECT_IDS.viewField,
},
relations: ['fields'],
},
);
if (!viewFieldObjectMetadata) {
this.logger.warn(
`ViewField object metadata not found for workspace ${workspaceId}`,
);
return;
}
const aggregateOperationField = viewFieldObjectMetadata.fields.find(
(field) => field.name === 'aggregateOperation',
);
if (!aggregateOperationField) {
this.logger.warn(
`aggregateOperation field not found for workspace ${workspaceId}`,
);
return;
}
await this.fieldMetadataRepository.update(
{ id: aggregateOperationField.id },
{ options: AGGREGATE_OPERATION_OPTIONS },
);
this.logger.log(
`Updated aggregateOperation options for workspace ${workspaceId}`,
);
await this.workspaceMigrationService.createCustomMigration(
generateMigrationName(`update-view-field-operations`),
workspaceId,
[
{
name: computeObjectTargetTable(viewFieldObjectMetadata),
action: WorkspaceMigrationTableActionType.ALTER,
columns: this.workspaceMigrationFactory.createColumnActions(
WorkspaceMigrationColumnActionType.ALTER,
{ ...aggregateOperationField, options: undefined },
{
...aggregateOperationField,
options: AGGREGATE_OPERATION_OPTIONS,
},
),
} satisfies WorkspaceMigrationTableAction,
],
);
}
}
@@ -1,37 +0,0 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { InitializePermissionsCommand } from 'src/database/commands/upgrade-version-command/0-44/0-44-initialize-permissions.command';
import { UpdateViewAggregateOperationsCommand } from 'src/database/commands/upgrade-version-command/0-44/0-44-update-view-aggregate-operations.command';
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { RoleModule } from 'src/engine/metadata-modules/role/role.module';
import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.module';
import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module';
import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module';
import { 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([Workspace, UserWorkspace], 'core'),
TypeOrmModule.forFeature(
[FieldMetadataEntity, ObjectMetadataEntity],
'metadata',
),
WorkspaceDataSourceModule,
RoleModule,
UserRoleModule,
WorkspaceMigrationModule,
WorkspaceMigrationRunnerModule,
WorkspaceMetadataVersionModule,
],
providers: [
InitializePermissionsCommand,
UpdateViewAggregateOperationsCommand,
],
exports: [InitializePermissionsCommand, UpdateViewAggregateOperationsCommand],
})
export class V0_44_UpgradeVersionCommandModule {}
@@ -1,32 +0,0 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { RoleModule } from 'src/engine/metadata-modules/role/role.module';
import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.module';
import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module';
import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module';
import { 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([Workspace, UserWorkspace], 'core'),
TypeOrmModule.forFeature(
[FieldMetadataEntity, ObjectMetadataEntity],
'metadata',
),
WorkspaceDataSourceModule,
RoleModule,
UserRoleModule,
WorkspaceMigrationModule,
WorkspaceMigrationRunnerModule,
WorkspaceMetadataVersionModule,
],
providers: [],
exports: [],
})
export class V0_50_UpgradeVersionCommandModule {}
@@ -1,112 +0,0 @@
import { InjectRepository } from '@nestjs/typeorm';
import { Command } from 'nest-commander';
import { isDefined } from 'twenty-shared/utils';
import { Repository } from 'typeorm';
import {
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
RunOnWorkspaceArgs,
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.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 { computeTableName } from 'src/engine/utils/compute-table-name.util';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
@Command({
name: 'upgrade:0-51:upgrade-created-by-enum',
description: 'Upgrade created by enum',
})
export class UpgradeCreatedByEnumCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
constructor(
@InjectRepository(Workspace, 'core')
protected readonly workspaceRepository: Repository<Workspace>,
@InjectRepository(ObjectMetadataEntity, 'metadata')
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
) {
super(workspaceRepository, twentyORMGlobalManager);
}
override async runOnWorkspace({
index,
total,
workspaceId,
}: RunOnWorkspaceArgs): Promise<void> {
this.logger.log(
`Running command for workspace ${workspaceId} ${index + 1}/${total}`,
);
const schemaName =
this.workspaceDataSourceService.getSchemaName(workspaceId);
const workspaceDataSource =
await this.twentyORMGlobalManager.getDataSourceForWorkspace({
workspaceId,
});
const objectMetadatas = await this.objectMetadataRepository.find({
where: {
workspaceId,
},
relations: ['fields'],
});
const queryRunner = workspaceDataSource?.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
for (const objectMetadata of objectMetadatas) {
if (
!isDefined(
objectMetadata.fields.find((field) => field.name === 'createdBy'),
)
) {
continue;
}
const tableToUpdate = computeTableName(
objectMetadata.nameSingular,
objectMetadata.isCustom,
);
// Set current column as text
await queryRunner.query(
`ALTER TABLE "${schemaName}"."${tableToUpdate}"
ALTER COLUMN "createdBySource" SET DATA TYPE text USING "createdBySource"::text`,
);
// Drop default value
await queryRunner.query(
`ALTER TABLE "${schemaName}"."${tableToUpdate}"
ALTER COLUMN "createdBySource" DROP DEFAULT`,
);
// Drop the old enum type
await queryRunner.query(
`DROP TYPE "${schemaName}"."${tableToUpdate}_createdBySource_enum"`,
);
await queryRunner.query(
`CREATE TYPE "${schemaName}"."${tableToUpdate}_createdBySource_enum" AS ENUM ('EMAIL', 'CALENDAR', 'WORKFLOW', 'API', 'IMPORT', 'MANUAL', 'SYSTEM', 'WEBHOOK')`,
);
// Re-apply the enum type
await queryRunner.query(
`ALTER TABLE "${schemaName}"."${tableToUpdate}"
ALTER COLUMN "createdBySource" SET DATA TYPE "${schemaName}"."${tableToUpdate}_createdBySource_enum" USING "createdBySource"::"${schemaName}"."${tableToUpdate}_createdBySource_enum"`,
);
}
await queryRunner.commitTransaction();
} catch (error) {
await queryRunner.rollbackTransaction();
throw error;
} finally {
await queryRunner.release();
}
}
}
@@ -1,22 +0,0 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
import { UpgradeCreatedByEnumCommand } from 'src/database/commands/upgrade-version-command/0-51/0-51-update-workflow-trigger-type-enum.command';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
@Module({
imports: [
TypeOrmModule.forFeature([Workspace], 'core'),
TypeOrmModule.forFeature(
[ObjectMetadataEntity, FieldMetadataEntity],
'metadata',
),
WorkspaceDataSourceModule,
],
providers: [UpgradeCreatedByEnumCommand],
exports: [UpgradeCreatedByEnumCommand],
})
export class V0_51_UpgradeVersionCommandModule {}
@@ -1,145 +0,0 @@
import { InjectRepository } from '@nestjs/typeorm';
import chalk from 'chalk';
import { Command } from 'nest-commander';
import { FieldMetadataType } from 'twenty-shared/types';
import { In, Repository } from 'typeorm';
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
import {
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
RunOnWorkspaceArgs,
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { Workspace } 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 {
RelationDirection,
deduceRelationDirection,
} from 'src/engine/utils/deduce-relation-direction.util';
import { isFieldMetadataEntityOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
@Command({
name: 'upgrade:0-52:migrate-relations-to-field-metadata',
description: 'Migrate relations to field metadata',
})
export class MigrateRelationsToFieldMetadataCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
constructor(
@InjectRepository(Workspace, 'core')
protected readonly workspaceRepository: Repository<Workspace>,
@InjectRepository(FieldMetadataEntity, 'metadata')
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
) {
super(workspaceRepository, twentyORMGlobalManager);
}
override async runOnWorkspace({
index,
total,
workspaceId,
}: RunOnWorkspaceArgs): Promise<void> {
this.logger.log(
`Running command for workspace ${workspaceId} ${index + 1}/${total}`,
);
const fieldMetadataCollection = await this.fieldMetadataRepository.find({
where: {
workspaceId,
type: In([FieldMetadataType.RELATION, FieldMetadataType.UUID]),
},
relations: ['fromRelationMetadata', 'toRelationMetadata'],
});
if (!fieldMetadataCollection.length) {
this.logger.log(
chalk.yellow(
`No relation field metadata found for workspace ${workspaceId}.`,
),
);
return;
}
const joinColumnFieldMetadataCollection = fieldMetadataCollection.filter(
(fieldMetadata) =>
isFieldMetadataEntityOfType(fieldMetadata, FieldMetadataType.UUID),
// TODO: Fix this, it's working in other places but not here
) as FieldMetadataEntity<FieldMetadataType.UUID>[];
const fieldMetadataToUpdateCollection = fieldMetadataCollection
.filter((fieldMetadata) =>
isFieldMetadataEntityOfType(fieldMetadata, FieldMetadataType.RELATION),
)
.map((fieldMetadata) =>
this.updateRelationFieldMetadata(
joinColumnFieldMetadataCollection,
// TODO: Fix this, it's working in other places but not here
fieldMetadata as FieldMetadataEntity<FieldMetadataType.RELATION>,
),
);
if (fieldMetadataToUpdateCollection.length > 0) {
await this.fieldMetadataRepository.save(fieldMetadataToUpdateCollection);
}
this.logger.log(
chalk.green(`Command completed for workspace ${workspaceId}.`),
);
}
private updateRelationFieldMetadata(
joinColumnFieldMetadataCollection: FieldMetadataEntity<FieldMetadataType.UUID>[],
fieldMetadata: FieldMetadataEntity<FieldMetadataType.RELATION>,
): FieldMetadataEntity<FieldMetadataType.RELATION> {
const relationMetadata =
fieldMetadata.fromRelationMetadata ?? fieldMetadata.toRelationMetadata;
const joinColumnFieldMetadata = joinColumnFieldMetadataCollection.find(
(joinColumnFieldMetadata) =>
// We're deducing the field based on the name of the relation field
// This is not the best way to do this but we don't have a better way
joinColumnFieldMetadata.name === `${fieldMetadata.name}Id`,
);
const relationDirection = deduceRelationDirection(
fieldMetadata,
relationMetadata,
);
let relationType = relationMetadata.relationType as unknown as RelationType;
if (
relationDirection === RelationDirection.TO &&
relationType === RelationType.ONE_TO_MANY
) {
relationType = RelationType.MANY_TO_ONE;
}
const relationTargetFieldMetadataId =
relationDirection === RelationDirection.FROM
? relationMetadata.toFieldMetadataId
: relationMetadata.fromFieldMetadataId;
const relationTargetObjectMetadataId =
relationDirection === RelationDirection.FROM
? relationMetadata.toObjectMetadataId
: relationMetadata.fromObjectMetadataId;
return {
...fieldMetadata,
settings: {
relationType,
onDelete:
relationType === RelationType.MANY_TO_ONE
? relationMetadata.onDeleteAction
: undefined,
joinColumnName:
relationType === RelationType.MANY_TO_ONE
? joinColumnFieldMetadata?.name
: undefined,
},
relationTargetFieldMetadataId,
relationTargetObjectMetadataId,
};
}
}
@@ -1,90 +0,0 @@
import { InjectRepository } from '@nestjs/typeorm';
import chalk from 'chalk';
import { Command } from 'nest-commander';
import { FieldMetadataType } from 'twenty-shared/types';
import { In, Repository } from 'typeorm';
import { isDefined } from 'twenty-shared/utils';
import { DateDisplayFormat } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
import {
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
RunOnWorkspaceArgs,
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
type DeprecatedFieldMetadataDateSettings = {
displayAsRelativeDate?: boolean;
};
@Command({
name: 'upgrade:0-52:upgrade-date-and-date-time-field-settings',
description: 'Upgrade settings column on all date and date time fields',
})
export class UpgradeDateAndDateTimeFieldsSettingsJsonCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
constructor(
@InjectRepository(Workspace, 'core')
protected readonly workspaceRepository: Repository<Workspace>,
@InjectRepository(ObjectMetadataEntity, 'metadata')
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
@InjectRepository(FieldMetadataEntity, 'metadata')
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
) {
super(workspaceRepository, twentyORMGlobalManager);
}
override async runOnWorkspace({
index,
total,
workspaceId,
}: RunOnWorkspaceArgs): Promise<void> {
this.logger.log(
`Running command for workspace ${workspaceId} ${index + 1}/${total}`,
);
const fieldMetadataCollection = (await this.fieldMetadataRepository.find({
where: {
workspaceId,
type: In([FieldMetadataType.DATE, FieldMetadataType.DATE_TIME]),
},
})) as FieldMetadataEntity<FieldMetadataType.DATE>[];
const updatedFieldMetadataCollection = fieldMetadataCollection.map(
(field) => this.updateDateAndDateTimeFieldMetadata(field),
);
if (updatedFieldMetadataCollection.length > 0) {
await this.fieldMetadataRepository.save(updatedFieldMetadataCollection);
}
this.logger.log(
chalk.green(`Command completed for workspace ${workspaceId}.`),
);
}
private updateDateAndDateTimeFieldMetadata(
field: FieldMetadataEntity<FieldMetadataType.DATE>,
): FieldMetadataEntity<FieldMetadataType.DATE> {
const settings = field.settings as DeprecatedFieldMetadataDateSettings;
if (!isDefined(settings?.displayAsRelativeDate)) {
return field;
}
return {
...field,
settings: {
displayFormat: settings.displayAsRelativeDate
? DateDisplayFormat.RELATIVE
: DateDisplayFormat.USER_SETTINGS,
},
};
}
}
@@ -1,29 +0,0 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { MigrateRelationsToFieldMetadataCommand } from 'src/database/commands/upgrade-version-command/0-52/0-52-migrate-relations-to-field-metadata.command';
import { UpgradeDateAndDateTimeFieldsSettingsJsonCommand } from 'src/database/commands/upgrade-version-command/0-52/0-52-upgrade-settings-field';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
@Module({
imports: [
TypeOrmModule.forFeature([Workspace], 'core'),
TypeOrmModule.forFeature(
[ObjectMetadataEntity, FieldMetadataEntity],
'metadata',
),
WorkspaceDataSourceModule,
],
providers: [
UpgradeDateAndDateTimeFieldsSettingsJsonCommand,
MigrateRelationsToFieldMetadataCommand,
],
exports: [
UpgradeDateAndDateTimeFieldsSettingsJsonCommand,
MigrateRelationsToFieldMetadataCommand,
],
})
export class V0_52_UpgradeVersionCommandModule {}
@@ -1,151 +0,0 @@
import { InjectRepository } from '@nestjs/typeorm';
import { Command } from 'nest-commander';
import { Repository } from 'typeorm';
import {
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
RunOnWorkspaceArgs,
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
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';
import { WorkflowTrigger } from 'src/modules/workflow/workflow-trigger/types/workflow-trigger.type';
@Command({
name: 'upgrade:0-53:backfill-workflow-next-step-ids',
description: 'Backfill workflow next step ids',
})
export class BackfillWorkflowNextStepIdsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
constructor(
@InjectRepository(Workspace, 'core')
protected readonly workspaceRepository: Repository<Workspace>,
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
) {
super(workspaceRepository, twentyORMGlobalManager);
}
override async runOnWorkspace({
index,
total,
workspaceId,
}: RunOnWorkspaceArgs): Promise<void> {
this.logger.log(
`Running command for workspace ${workspaceId} ${index + 1}/${total}`,
);
const workflowVersionRepository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowVersionWorkspaceEntity>(
workspaceId,
'workflowVersion',
);
const workflowRunRepository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowRunWorkspaceEntity>(
workspaceId,
'workflowRun',
);
const workflowVersions = await workflowVersionRepository.find();
this.logger.log(`Number of workflow versions: ${workflowVersions.length}`);
for (const workflowVersion of workflowVersions) {
this.logger.log(`Processing workflow version: ${workflowVersion.id}`);
const updatedSteps: WorkflowVersionWorkspaceEntity['steps'] = [];
const workflowSteps = workflowVersion.steps;
if (!workflowSteps) {
continue;
}
// for each step, add the next step id which is the next index
for (let stepIndex = 0; stepIndex < workflowSteps.length; stepIndex++) {
const updatedStep = {
...workflowSteps[stepIndex],
nextStepIds:
stepIndex < workflowSteps.length - 1 &&
workflowSteps[stepIndex + 1]?.id
? [workflowSteps[stepIndex + 1].id]
: undefined,
};
updatedSteps.push(updatedStep);
}
// update workflow run flows by batch of 500
const batchSize = 500;
const totalWorkflowRuns = await workflowRunRepository.count({
where: {
workflowVersionId: workflowVersion.id,
},
});
const totalBatches = Math.ceil(totalWorkflowRuns / batchSize);
this.logger.log(`Total batches: ${totalBatches}`);
for (let batchIndex = 0; batchIndex < totalBatches; batchIndex++) {
const updatedWorkflowRuns: WorkflowRunWorkspaceEntity[] = [];
this.logger.log(`Processing batch ${batchIndex + 1}/${totalBatches}`);
const workflowRuns = await workflowRunRepository.find({
where: {
workflowVersionId: workflowVersion.id,
},
take: batchSize,
skip: batchIndex * batchSize,
order: {
id: 'ASC',
},
});
for (const workflowRun of workflowRuns) {
const flow = workflowRun.output?.flow;
if (!flow?.steps || flow.steps.length < 2) {
continue;
}
const updatedStepsMap = new Map(
updatedSteps.map((step) => [step.id, step]),
);
const updatedFlow = flow.steps.map((step) => {
const updatedStep = updatedStepsMap.get(step.id);
return {
...step,
nextStepIds: updatedStep?.nextStepIds,
};
});
const updatedWorkflowRun: WorkflowRunWorkspaceEntity = {
...workflowRun,
output: {
...workflowRun.output,
flow: {
trigger: workflowRun.output?.flow?.trigger as WorkflowTrigger,
steps: updatedFlow,
},
},
};
updatedWorkflowRuns.push(updatedWorkflowRun);
}
await workflowRunRepository.save(updatedWorkflowRuns);
}
await workflowVersionRepository.save({
...workflowVersion,
steps: updatedSteps,
});
this.logger.log(
`Updated workflow version ${workflowVersion.id} for workspace ${workspaceId}`,
);
}
}
}
@@ -1,108 +0,0 @@
import { InjectRepository } from '@nestjs/typeorm';
import { Command } from 'nest-commander';
import { Repository } from 'typeorm';
import {
MigrationCommandOptions,
MigrationCommandRunner,
} from 'src/database/commands/command-runners/migration.command-runner';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
@Command({
name: 'upgrade:0-53:copy-typeorm-migrations',
description: 'Copy _typeorm_migrations from metadata schema to core schema',
})
export class CopyTypeormMigrationsCommand extends MigrationCommandRunner {
constructor(
@InjectRepository(Workspace, 'core')
protected readonly workspaceRepository: Repository<Workspace>,
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
) {
super();
}
override async runMigrationCommand(
_passedParams: string[],
options: MigrationCommandOptions,
): Promise<void> {
this.logger.log(
'Starting to copy _typeorm_migrations from metadata to core',
);
const queryRunner =
this.workspaceRepository.manager.connection.createQueryRunner();
try {
await queryRunner.connect();
await queryRunner.startTransaction();
// Check if metadata._typeorm_migrations table exists
const tableExists = await queryRunner.query(
`SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_schema = 'metadata'
AND table_name = '_typeorm_migrations'
)`,
);
if (!tableExists[0].exists) {
this.logger.log(
'metadata._typeorm_migrations table does not exist, skipping migration',
);
await queryRunner.commitTransaction();
return;
}
const metadataMigrations = await queryRunner.query(
'SELECT * FROM metadata._typeorm_migrations ORDER BY id ASC',
);
this.logger.log(
`Found ${metadataMigrations.length} migrations in metadata schema`,
);
if (options?.dryRun) {
this.logger.log('Dry run mode - no changes will be applied');
return;
}
const existingCoreMigrations = await queryRunner.query(
'SELECT name FROM core._typeorm_migrations',
);
const existingMigrationNames = new Set(
// @ts-expect-error legacy noImplicitAny
existingCoreMigrations.map((migration) => migration.name),
);
for (const migration of metadataMigrations) {
if (!existingMigrationNames.has(migration.name)) {
await queryRunner.query(
'INSERT INTO core._typeorm_migrations ("timestamp", name) VALUES ($1, $2)',
[migration.timestamp, migration.name],
);
this.logger.log(`Copied migration: ${migration.name}`);
} else {
this.logger.log(
`Migration ${migration.name} already exists in core schema`,
);
}
}
await queryRunner.commitTransaction();
this.logger.log(
'Successfully copied all migrations from metadata to core schema',
);
} catch (error) {
await queryRunner.rollbackTransaction();
this.logger.error(`Failed to copy migrations: ${error.message}`);
throw error;
} finally {
await queryRunner.release();
}
}
}
@@ -1,71 +0,0 @@
import { InjectRepository } from '@nestjs/typeorm';
import { Command } from 'nest-commander';
import { Repository } from 'typeorm';
import {
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
RunOnWorkspaceArgs,
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import {
AutomatedTriggerType,
WorkflowAutomatedTriggerWorkspaceEntity,
} from 'src/modules/workflow/common/standard-objects/workflow-automated-trigger.workspace-entity';
@Command({
name: 'upgrade:0-53:migrate-workflow-event-listeners-to-automated-triggers',
description: 'Migrate workflow event listeners to automated triggers',
})
export class MigrateWorkflowEventListenersToAutomatedTriggersCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
constructor(
@InjectRepository(Workspace, 'core')
protected readonly workspaceRepository: Repository<Workspace>,
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
) {
super(workspaceRepository, twentyORMGlobalManager);
}
override async runOnWorkspace({
index,
total,
workspaceId,
}: RunOnWorkspaceArgs): Promise<void> {
this.logger.log(
`Running command for workspace ${workspaceId} ${index + 1}/${total}`,
);
const workflowEventListenerRepository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
workspaceId,
'workflowEventListener',
);
const workflowAutomatedTriggerRepository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowAutomatedTriggerWorkspaceEntity>(
workspaceId,
'workflowAutomatedTrigger',
);
const workflowEventListeners = await workflowEventListenerRepository.find();
await workflowAutomatedTriggerRepository.delete({
type: AutomatedTriggerType.DATABASE_EVENT,
});
for (const eventListener of workflowEventListeners) {
const { eventName, ...rest } = eventListener;
await workflowAutomatedTriggerRepository.save({
...rest,
type: AutomatedTriggerType.DATABASE_EVENT,
settings: { eventName },
});
}
this.logger.log(
`Migrated ${workflowEventListeners.length} workflow event listeners to automated triggers`,
);
}
}
@@ -1,84 +0,0 @@
import { InjectRepository } from '@nestjs/typeorm';
import chalk from 'chalk';
import { Command } from 'nest-commander';
import { FieldMetadataType } from 'twenty-shared/types';
import { In, Like, Repository } from 'typeorm';
import {
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
RunOnWorkspaceArgs,
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { Workspace } 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 { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
@Command({
name: 'upgrade:0-53:remove-relation-foreign-key-field-metadata',
description: 'Remove relation foreign key from field metadata',
})
export class RemoveRelationForeignKeyFieldMetadataCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
constructor(
@InjectRepository(Workspace, 'core')
protected readonly workspaceRepository: Repository<Workspace>,
protected readonly featureFlagService: FeatureFlagService,
@InjectRepository(FieldMetadataEntity, 'metadata')
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
) {
super(workspaceRepository, twentyORMGlobalManager);
}
override async runOnWorkspace({
index,
total,
workspaceId,
options,
}: RunOnWorkspaceArgs): Promise<void> {
this.logger.log(
`Running command for workspace ${workspaceId} ${index + 1}/${total}`,
);
const fieldMetadataCollection = await this.fieldMetadataRepository.find({
where: {
workspaceId,
type: In([FieldMetadataType.UUID]),
label: Like('%(foreign key)%'),
},
});
if (!fieldMetadataCollection.length) {
this.logger.log(
chalk.yellow(
`No relation field metadata found for workspace ${workspaceId}.`,
),
);
return;
}
if (options.dryRun) {
this.logger.log(
chalk.yellow(
`Dry run, would delete ${fieldMetadataCollection.length} relation field metadata for workspace ${workspaceId}.`,
),
);
} else {
await this.featureFlagService.enableFeatureFlags(
['IS_NEW_RELATION_ENABLED' as FeatureFlagKey],
workspaceId,
);
await this.fieldMetadataRepository.delete({
id: In(
fieldMetadataCollection.map((fieldMetadata) => fieldMetadata.id),
),
});
}
this.logger.log(
chalk.green(`Command completed for workspace ${workspaceId}.`),
);
}
}
@@ -1,76 +0,0 @@
import { InjectRepository } from '@nestjs/typeorm';
import { Command } from 'nest-commander';
import { Repository } from 'typeorm';
import {
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
RunOnWorkspaceArgs,
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { SearchVectorService } from 'src/engine/metadata-modules/search-vector/search-vector.service';
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 { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service';
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
import { SEARCH_FIELDS_FOR_PERSON } from 'src/modules/person/standard-objects/person.workspace-entity';
@Command({
name: 'upgrade:0-53:upgrade-search-vector-on-person-entity',
description: 'Upgrade search vector on person entity',
})
export class UpgradeSearchVectorOnPersonEntityCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
constructor(
@InjectRepository(Workspace, 'core')
protected readonly workspaceRepository: Repository<Workspace>,
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
@InjectRepository(ObjectMetadataEntity, 'metadata')
protected readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
private readonly searchVectorService: SearchVectorService,
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
) {
super(workspaceRepository, twentyORMGlobalManager);
}
override async runOnWorkspace({
index,
total,
workspaceId,
options,
}: RunOnWorkspaceArgs): Promise<void> {
this.logger.log(
`Running command for workspace ${workspaceId} ${index + 1}/${total}`,
);
const personObjectMetadata =
await this.objectMetadataRepository.findOneOrFail({
select: ['id'],
where: {
workspaceId,
standardId: STANDARD_OBJECT_IDS.person,
},
});
if (!options.dryRun) {
await this.searchVectorService.updateSearchVector(
personObjectMetadata.id,
SEARCH_FIELDS_FOR_PERSON,
workspaceId,
);
await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations(
workspaceId,
);
await this.workspaceMetadataVersionService.incrementMetadataVersion(
workspaceId,
);
}
this.logger.log(
`Migrated search vector on person entity for workspace ${workspaceId}`,
);
}
}
@@ -1,46 +0,0 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { BackfillWorkflowNextStepIdsCommand } from 'src/database/commands/upgrade-version-command/0-53/0-53-backfill-workflow-next-step-ids.command';
import { CopyTypeormMigrationsCommand } from 'src/database/commands/upgrade-version-command/0-53/0-53-copy-typeorm-migrations.command';
import { MigrateWorkflowEventListenersToAutomatedTriggersCommand } from 'src/database/commands/upgrade-version-command/0-53/0-53-migrate-workflow-event-listeners-to-automated-triggers.command';
import { RemoveRelationForeignKeyFieldMetadataCommand } from 'src/database/commands/upgrade-version-command/0-53/0-53-remove-relation-foreign-key-field-metadata.command';
import { UpgradeSearchVectorOnPersonEntityCommand } from 'src/database/commands/upgrade-version-command/0-53/0-53-upgrade-search-vector-on-person-entity.command';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { SearchVectorModule } from 'src/engine/metadata-modules/search-vector/search-vector.module';
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([Workspace], 'core'),
TypeOrmModule.forFeature(
[FieldMetadataEntity, ObjectMetadataEntity],
'metadata',
),
WorkspaceDataSourceModule,
SearchVectorModule,
WorkspaceMigrationRunnerModule,
WorkspaceMetadataVersionModule,
FeatureFlagModule,
],
providers: [
MigrateWorkflowEventListenersToAutomatedTriggersCommand,
CopyTypeormMigrationsCommand,
BackfillWorkflowNextStepIdsCommand,
RemoveRelationForeignKeyFieldMetadataCommand,
UpgradeSearchVectorOnPersonEntityCommand,
],
exports: [
MigrateWorkflowEventListenersToAutomatedTriggersCommand,
RemoveRelationForeignKeyFieldMetadataCommand,
BackfillWorkflowNextStepIdsCommand,
CopyTypeormMigrationsCommand,
UpgradeSearchVectorOnPersonEntityCommand,
],
})
export class V0_53_UpgradeVersionCommandModule {}
@@ -25,7 +25,7 @@ export class FixCreatedByDefaultValueCommand extends ActiveOrSuspendedWorkspaces
@InjectRepository(Workspace, 'core')
protected readonly workspaceRepository: Repository<Workspace>,
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
@InjectRepository(ObjectMetadataEntity, 'metadata')
@InjectRepository(ObjectMetadataEntity, 'core')
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
) {
@@ -22,7 +22,7 @@ export class FixStandardSelectFieldsPositionCommand extends ActiveOrSuspendedWor
@InjectRepository(Workspace, 'core')
protected readonly workspaceRepository: Repository<Workspace>,
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
@InjectRepository(FieldMetadataEntity, 'metadata')
@InjectRepository(FieldMetadataEntity, 'core')
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
) {
@@ -26,7 +26,7 @@ import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/wor
),
TypeOrmModule.forFeature(
[FieldMetadataEntity, ObjectMetadataEntity],
'metadata',
'core',
),
WorkspaceDataSourceModule,
WorkspaceMigrationRunnerModule,
@@ -15,13 +15,16 @@ import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/wor
@Module({
imports: [
TypeOrmModule.forFeature(
[Workspace, AppToken, User, UserWorkspace],
[
Workspace,
AppToken,
User,
UserWorkspace,
FieldMetadataEntity,
ObjectMetadataEntity,
],
'core',
),
TypeOrmModule.forFeature(
[FieldMetadataEntity, ObjectMetadataEntity],
'metadata',
),
WorkspaceDataSourceModule,
WorkspaceMigrationRunnerModule,
WorkspaceMetadataVersionModule,
@@ -1,11 +1,6 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { V0_44_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/0-44/0-44-upgrade-version-command.module';
import { V0_50_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/0-50/0-50-upgrade-version-command.module';
import { V0_51_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/0-51/0-51-upgrade-version-command.module';
import { V0_52_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/0-52/0-52-upgrade-version-command.module';
import { V0_53_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/0-53/0-53-upgrade-version-command.module';
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 {
@@ -18,11 +13,6 @@ import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/worksp
@Module({
imports: [
TypeOrmModule.forFeature([Workspace], 'core'),
V0_44_UpgradeVersionCommandModule,
V0_50_UpgradeVersionCommandModule,
V0_51_UpgradeVersionCommandModule,
V0_52_UpgradeVersionCommandModule,
V0_53_UpgradeVersionCommandModule,
V0_54_UpgradeVersionCommandModule,
V0_55_UpgradeVersionCommandModule,
WorkspaceSyncMetadataModule,
@@ -15,16 +15,6 @@ import {
UpgradeCommandRunner,
VersionCommands,
} from 'src/database/commands/command-runners/upgrade.command-runner';
import { InitializePermissionsCommand } from 'src/database/commands/upgrade-version-command/0-44/0-44-initialize-permissions.command';
import { UpdateViewAggregateOperationsCommand } from 'src/database/commands/upgrade-version-command/0-44/0-44-update-view-aggregate-operations.command';
import { UpgradeCreatedByEnumCommand } from 'src/database/commands/upgrade-version-command/0-51/0-51-update-workflow-trigger-type-enum.command';
import { MigrateRelationsToFieldMetadataCommand } from 'src/database/commands/upgrade-version-command/0-52/0-52-migrate-relations-to-field-metadata.command';
import { UpgradeDateAndDateTimeFieldsSettingsJsonCommand } from 'src/database/commands/upgrade-version-command/0-52/0-52-upgrade-settings-field';
import { BackfillWorkflowNextStepIdsCommand } from 'src/database/commands/upgrade-version-command/0-53/0-53-backfill-workflow-next-step-ids.command';
import { CopyTypeormMigrationsCommand } from 'src/database/commands/upgrade-version-command/0-53/0-53-copy-typeorm-migrations.command';
import { MigrateWorkflowEventListenersToAutomatedTriggersCommand } from 'src/database/commands/upgrade-version-command/0-53/0-53-migrate-workflow-event-listeners-to-automated-triggers.command';
import { RemoveRelationForeignKeyFieldMetadataCommand } from 'src/database/commands/upgrade-version-command/0-53/0-53-remove-relation-foreign-key-field-metadata.command';
import { UpgradeSearchVectorOnPersonEntityCommand } from 'src/database/commands/upgrade-version-command/0-53/0-53-upgrade-search-vector-on-person-entity.command';
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';
@@ -45,7 +35,6 @@ export class DatabaseMigrationService {
constructor(
@InjectRepository(Workspace, 'core')
private readonly workspaceRepository: Repository<Workspace>,
protected readonly copyTypeormMigrationsCommand: CopyTypeormMigrationsCommand,
) {}
// TODO centralize with ActiveOrSuspendedRunner method
@@ -75,17 +64,6 @@ export class DatabaseMigrationService {
this.logger.log('Running global database migrations');
try {
this.logger.log('Running metadata datasource migrations...');
await this.copyTypeormMigrationsCommand.runMigrationCommand([], {
dryRun: false,
verbose: false,
});
const metadataResult = await execPromise(
'npx -y typeorm migration:run -d dist/src/database/typeorm/metadata/metadata.datasource',
);
this.logger.log(metadataResult.stdout);
this.logger.log('Running core datasource migrations...');
const coreResult = await execPromise(
'npx -y typeorm migration:run -d dist/src/database/typeorm/core/core.datasource',
@@ -147,24 +125,6 @@ export class UpgradeCommand extends UpgradeCommandRunner {
private readonly databaseMigrationService: DatabaseMigrationService,
// 0.44 Commands
protected readonly initializePermissionsCommand: InitializePermissionsCommand,
protected readonly updateViewAggregateOperationsCommand: UpdateViewAggregateOperationsCommand,
// 0.51 Commands
protected readonly upgradeCreatedByEnumCommand: UpgradeCreatedByEnumCommand,
// 0.52 Commands
protected readonly upgradeDateAndDateTimeFieldsSettingsJsonCommand: UpgradeDateAndDateTimeFieldsSettingsJsonCommand,
protected readonly migrateRelationsToFieldMetadataCommand: MigrateRelationsToFieldMetadataCommand,
// 0.53 Commands
protected readonly migrateWorkflowEventListenersToAutomatedTriggersCommand: MigrateWorkflowEventListenersToAutomatedTriggersCommand,
protected readonly backfillWorkflowNextStepIdsCommand: BackfillWorkflowNextStepIdsCommand,
protected readonly copyTypeormMigrationsCommand: CopyTypeormMigrationsCommand,
protected readonly upgradeSearchVectorOnPersonEntityCommand: UpgradeSearchVectorOnPersonEntityCommand,
protected readonly removeRelationForeignKeyFieldMetadataCommand: RemoveRelationForeignKeyFieldMetadataCommand,
// 0.54 Commands
protected readonly fixStandardSelectFieldsPositionCommand: FixStandardSelectFieldsPositionCommand,
protected readonly fixCreatedByDefaultValueCommand: FixCreatedByDefaultValueCommand,
@@ -179,41 +139,6 @@ export class UpgradeCommand extends UpgradeCommandRunner {
syncWorkspaceMetadataCommand,
);
const commands_044: VersionCommands = {
beforeSyncMetadata: [
this.initializePermissionsCommand,
this.updateViewAggregateOperationsCommand,
],
afterSyncMetadata: [],
};
const commands_050: VersionCommands = {
beforeSyncMetadata: [],
afterSyncMetadata: [],
};
const commands_051: VersionCommands = {
beforeSyncMetadata: [this.upgradeCreatedByEnumCommand],
afterSyncMetadata: [],
};
const commands_052: VersionCommands = {
beforeSyncMetadata: [
this.upgradeDateAndDateTimeFieldsSettingsJsonCommand,
this.migrateRelationsToFieldMetadataCommand,
],
afterSyncMetadata: [],
};
const commands_053: VersionCommands = {
beforeSyncMetadata: [this.removeRelationForeignKeyFieldMetadataCommand],
afterSyncMetadata: [
this.migrateWorkflowEventListenersToAutomatedTriggersCommand,
this.backfillWorkflowNextStepIdsCommand,
this.upgradeSearchVectorOnPersonEntityCommand,
],
};
const commands_054: VersionCommands = {
beforeSyncMetadata: [
this.fixStandardSelectFieldsPositionCommand,
@@ -227,11 +152,6 @@ export class UpgradeCommand extends UpgradeCommandRunner {
};
this.allCommands = {
'0.44.0': commands_044,
'0.50.0': commands_050,
'0.51.0': commands_051,
'0.52.0': commands_052,
'0.53.0': commands_053,
'0.54.0': commands_054,
};
}