Workspace commands writes inupgradeMigration (#19379)
# Introduction As for the instance commands we want to keep a track of what has been run for the workspace commands Note that the history will be updated only when the workspace command has been run through the upgrade directly and not when run atomically ## What's next Later we will use this history in order to determine the current workspace's version and instance's version getting rid of the version in database, that will be the last stone
This commit is contained in:
+43
@@ -0,0 +1,43 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddWorkspaceIdToUpgradeMigration1775553825848
|
||||
implements MigrationInterface
|
||||
{
|
||||
name = 'AddWorkspaceIdToUpgradeMigration1775553825848';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
'ALTER TABLE "core"."upgradeMigration" DROP CONSTRAINT "UQ_upgrade_migration_name_attempt"',
|
||||
);
|
||||
await queryRunner.query(
|
||||
'ALTER TABLE "core"."upgradeMigration" ADD "workspaceId" uuid',
|
||||
);
|
||||
await queryRunner.query(
|
||||
'CREATE UNIQUE INDEX "UQ_upgrade_migration_workspace" ON "core"."upgradeMigration" ("name", "attempt", "workspaceId") WHERE "workspaceId" IS NOT NULL',
|
||||
);
|
||||
await queryRunner.query(
|
||||
'CREATE UNIQUE INDEX "UQ_upgrade_migration_instance" ON "core"."upgradeMigration" ("name", "attempt") WHERE "workspaceId" IS NULL',
|
||||
);
|
||||
await queryRunner.query(
|
||||
'ALTER TABLE "core"."upgradeMigration" ADD CONSTRAINT "FK_77f64a697c55f8802592bd7eeba" FOREIGN KEY ("workspaceId") REFERENCES "core"."workspace"("id") ON DELETE CASCADE ON UPDATE NO ACTION',
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
'ALTER TABLE "core"."upgradeMigration" DROP CONSTRAINT "FK_77f64a697c55f8802592bd7eeba"',
|
||||
);
|
||||
await queryRunner.query(
|
||||
'DROP INDEX "core"."UQ_upgrade_migration_instance"',
|
||||
);
|
||||
await queryRunner.query(
|
||||
'DROP INDEX "core"."UQ_upgrade_migration_workspace"',
|
||||
);
|
||||
await queryRunner.query(
|
||||
'ALTER TABLE "core"."upgradeMigration" DROP COLUMN "workspaceId"',
|
||||
);
|
||||
await queryRunner.query(
|
||||
'ALTER TABLE "core"."upgradeMigration" ADD CONSTRAINT "UQ_upgrade_migration_name_attempt" UNIQUE ("name", "attempt")',
|
||||
);
|
||||
}
|
||||
}
|
||||
+15
-62
@@ -1,18 +1,10 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||
import { InjectDataSource } from '@nestjs/typeorm';
|
||||
|
||||
import {
|
||||
DataSource,
|
||||
MigrationInterface,
|
||||
type QueryRunner,
|
||||
Repository,
|
||||
} from 'typeorm';
|
||||
import { DataSource, MigrationInterface } from 'typeorm';
|
||||
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import {
|
||||
UpgradeMigrationEntity,
|
||||
type UpgradeMigrationStatus,
|
||||
} from 'src/engine/core-modules/upgrade/upgrade-migration.entity';
|
||||
import { UpgradeMigrationService } from 'src/engine/core-modules/upgrade/services/upgrade-migration.service';
|
||||
|
||||
export type RunSingleMigrationResult =
|
||||
| { status: 'success' }
|
||||
@@ -22,11 +14,10 @@ export type RunSingleMigrationResult =
|
||||
@Injectable()
|
||||
export class InstanceUpgradeService {
|
||||
constructor(
|
||||
@InjectRepository(UpgradeMigrationEntity)
|
||||
private readonly upgradeMigrationRepository: Repository<UpgradeMigrationEntity>,
|
||||
@InjectDataSource()
|
||||
private readonly dataSource: DataSource,
|
||||
private readonly twentyConfigService: TwentyConfigService,
|
||||
private readonly upgradeMigrationService: UpgradeMigrationService,
|
||||
) {}
|
||||
|
||||
async runSingleMigration(
|
||||
@@ -36,11 +27,13 @@ export class InstanceUpgradeService {
|
||||
const executedByVersion =
|
||||
this.twentyConfigService.get('APP_VERSION') ?? 'unknown';
|
||||
|
||||
const isAlreadyExecuted = await this.upgradeMigrationRepository.exists({
|
||||
where: { name: migrationName, status: 'completed' },
|
||||
});
|
||||
const isAlreadyCompleted =
|
||||
await this.upgradeMigrationService.isLastAttemptCompleted({
|
||||
name: migrationName,
|
||||
workspaceId: null,
|
||||
});
|
||||
|
||||
if (isAlreadyExecuted) {
|
||||
if (isAlreadyCompleted) {
|
||||
return { status: 'already-executed' };
|
||||
}
|
||||
|
||||
@@ -52,10 +45,11 @@ export class InstanceUpgradeService {
|
||||
|
||||
await migration.up(queryRunner);
|
||||
|
||||
await this.markAsCompleted({
|
||||
queryRunner,
|
||||
await this.upgradeMigrationService.markAsCompleted({
|
||||
name: migrationName,
|
||||
workspaceId: null,
|
||||
executedByVersion,
|
||||
queryRunner,
|
||||
});
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
@@ -64,8 +58,9 @@ export class InstanceUpgradeService {
|
||||
await queryRunner.rollbackTransaction();
|
||||
}
|
||||
|
||||
await this.markFailed({
|
||||
await this.upgradeMigrationService.markAsFailed({
|
||||
name: migrationName,
|
||||
workspaceId: null,
|
||||
executedByVersion,
|
||||
});
|
||||
|
||||
@@ -76,46 +71,4 @@ export class InstanceUpgradeService {
|
||||
|
||||
return { status: 'success' };
|
||||
}
|
||||
|
||||
private async markAsCompleted({
|
||||
queryRunner,
|
||||
name,
|
||||
executedByVersion,
|
||||
}: {
|
||||
queryRunner: QueryRunner;
|
||||
name: string;
|
||||
executedByVersion: string;
|
||||
}): Promise<void> {
|
||||
const repository = queryRunner.manager.getRepository(
|
||||
UpgradeMigrationEntity,
|
||||
);
|
||||
|
||||
const previousAttempts = await repository.count({ where: { name } });
|
||||
|
||||
await repository.save({
|
||||
name,
|
||||
status: 'completed' as UpgradeMigrationStatus,
|
||||
attempt: previousAttempts + 1,
|
||||
executedByVersion,
|
||||
});
|
||||
}
|
||||
|
||||
private async markFailed({
|
||||
name,
|
||||
executedByVersion,
|
||||
}: {
|
||||
name: string;
|
||||
executedByVersion: string;
|
||||
}): Promise<void> {
|
||||
const previousAttempts = await this.upgradeMigrationRepository.count({
|
||||
where: { name },
|
||||
});
|
||||
|
||||
await this.upgradeMigrationRepository.save({
|
||||
name,
|
||||
status: 'failed' as UpgradeMigrationStatus,
|
||||
attempt: previousAttempts + 1,
|
||||
executedByVersion,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { IsNull, type QueryRunner, Repository } from 'typeorm';
|
||||
|
||||
import { UpgradeMigrationEntity } from 'src/engine/core-modules/upgrade/upgrade-migration.entity';
|
||||
|
||||
@Injectable()
|
||||
export class UpgradeMigrationService {
|
||||
constructor(
|
||||
@InjectRepository(UpgradeMigrationEntity)
|
||||
private readonly upgradeMigrationRepository: Repository<UpgradeMigrationEntity>,
|
||||
) {}
|
||||
|
||||
async isLastAttemptCompleted({
|
||||
name,
|
||||
workspaceId,
|
||||
}: {
|
||||
name: string;
|
||||
workspaceId: string | null;
|
||||
}): Promise<boolean> {
|
||||
const latestAttempt = await this.upgradeMigrationRepository.findOne({
|
||||
where: {
|
||||
name,
|
||||
workspaceId: workspaceId === null ? IsNull() : workspaceId,
|
||||
},
|
||||
order: { attempt: 'DESC' },
|
||||
});
|
||||
|
||||
return isDefined(latestAttempt) && latestAttempt.status === 'completed';
|
||||
}
|
||||
|
||||
async markAsCompleted({
|
||||
name,
|
||||
workspaceId,
|
||||
executedByVersion,
|
||||
queryRunner,
|
||||
}: {
|
||||
name: string;
|
||||
workspaceId: string | null;
|
||||
executedByVersion: string;
|
||||
queryRunner?: QueryRunner;
|
||||
}): Promise<void> {
|
||||
const repository = queryRunner
|
||||
? queryRunner.manager.getRepository(UpgradeMigrationEntity)
|
||||
: this.upgradeMigrationRepository;
|
||||
const previousAttempts = await repository.count({
|
||||
where: {
|
||||
name,
|
||||
workspaceId: workspaceId === null ? IsNull() : workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
await repository.save({
|
||||
name,
|
||||
status: 'completed',
|
||||
attempt: previousAttempts + 1,
|
||||
executedByVersion,
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
async markAsFailed({
|
||||
name,
|
||||
workspaceId,
|
||||
executedByVersion,
|
||||
}: {
|
||||
name: string;
|
||||
workspaceId: string | null;
|
||||
executedByVersion: string;
|
||||
}): Promise<void> {
|
||||
const previousAttempts = await this.upgradeMigrationRepository.count({
|
||||
where: {
|
||||
name,
|
||||
workspaceId: workspaceId === null ? IsNull() : workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
await this.upgradeMigrationRepository.save({
|
||||
name,
|
||||
status: 'failed',
|
||||
attempt: previousAttempts + 1,
|
||||
executedByVersion,
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
}
|
||||
+71
-7
@@ -5,12 +5,13 @@ import { SemVer } from 'semver';
|
||||
import { assertUnreachable, isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { type WorkspaceIteratorContext } from 'src/database/commands/command-runners/workspace-iterator.service';
|
||||
import {
|
||||
type UpgradeCommandOptions,
|
||||
type VersionCommands,
|
||||
} from 'src/database/commands/command-runners/upgrade.command-runner';
|
||||
import { type RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspace.command-runner';
|
||||
import { type WorkspaceIteratorContext } from 'src/database/commands/command-runners/workspace-iterator.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { UpgradeMigrationService } from 'src/engine/core-modules/upgrade/services/upgrade-migration.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import {
|
||||
type CompareVersionMajorAndMinorReturnType,
|
||||
@@ -32,6 +33,8 @@ export class WorkspaceUpgradeService {
|
||||
constructor(
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
private readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
private readonly twentyConfigService: TwentyConfigService,
|
||||
private readonly upgradeMigrationService: UpgradeMigrationService,
|
||||
) {}
|
||||
|
||||
async upgradeWorkspace({
|
||||
@@ -60,13 +63,16 @@ export class WorkspaceUpgradeService {
|
||||
);
|
||||
}
|
||||
case 'equal': {
|
||||
const executedByVersion =
|
||||
this.twentyConfigService.get('APP_VERSION') ?? 'unknown';
|
||||
|
||||
for (const workspaceCommand of workspaceCommands) {
|
||||
await workspaceCommand.runOnWorkspace({
|
||||
options: options as RunOnWorkspaceArgs['options'],
|
||||
await this.runSingleWorkspaceCommandOrThrow({
|
||||
workspaceCommand,
|
||||
workspaceId,
|
||||
dataSource: iteratorContext.dataSource,
|
||||
index,
|
||||
total,
|
||||
executedByVersion,
|
||||
options,
|
||||
iteratorContext,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -112,4 +118,62 @@ export class WorkspaceUpgradeService {
|
||||
fromWorkspaceVersion.version,
|
||||
);
|
||||
}
|
||||
|
||||
private async runSingleWorkspaceCommandOrThrow({
|
||||
workspaceCommand,
|
||||
workspaceId,
|
||||
executedByVersion,
|
||||
options,
|
||||
iteratorContext,
|
||||
}: {
|
||||
workspaceCommand: VersionCommands[number];
|
||||
workspaceId: string;
|
||||
executedByVersion: string;
|
||||
options: UpgradeCommandOptions;
|
||||
iteratorContext: WorkspaceIteratorContext;
|
||||
}): Promise<void> {
|
||||
const commandName = workspaceCommand.constructor.name;
|
||||
|
||||
const isAlreadyCompleted =
|
||||
await this.upgradeMigrationService.isLastAttemptCompleted({
|
||||
name: commandName,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
if (isAlreadyCompleted) {
|
||||
this.logger.log(
|
||||
`Workspace command ${commandName} already completed for workspace ${workspaceId}, skipping`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await workspaceCommand.runOnWorkspace({
|
||||
options,
|
||||
workspaceId,
|
||||
dataSource: iteratorContext.dataSource,
|
||||
index: iteratorContext.index,
|
||||
total: iteratorContext.total,
|
||||
});
|
||||
|
||||
if (!options.dryRun) {
|
||||
await this.upgradeMigrationService.markAsCompleted({
|
||||
name: commandName,
|
||||
workspaceId,
|
||||
executedByVersion,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
if (!options.dryRun) {
|
||||
await this.upgradeMigrationService.markAsFailed({
|
||||
name: commandName,
|
||||
workspaceId,
|
||||
executedByVersion,
|
||||
});
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,26 @@ import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
Index,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
Unique,
|
||||
type Relation,
|
||||
} from 'typeorm';
|
||||
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
|
||||
export type UpgradeMigrationStatus = 'completed' | 'failed';
|
||||
|
||||
@Entity({ name: 'upgradeMigration', schema: 'core' })
|
||||
@Unique('UQ_upgrade_migration_name_attempt', ['name', 'attempt'])
|
||||
@Index('UQ_upgrade_migration_instance', ['name', 'attempt'], {
|
||||
unique: true,
|
||||
where: '"workspaceId" IS NULL',
|
||||
})
|
||||
@Index('UQ_upgrade_migration_workspace', ['name', 'attempt', 'workspaceId'], {
|
||||
unique: true,
|
||||
where: '"workspaceId" IS NOT NULL',
|
||||
})
|
||||
export class UpgradeMigrationEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
@@ -26,6 +38,13 @@ export class UpgradeMigrationEntity {
|
||||
@Column({ type: 'varchar', nullable: false })
|
||||
executedByVersion: string;
|
||||
|
||||
@ManyToOne(() => WorkspaceEntity, { onDelete: 'CASCADE', nullable: true })
|
||||
@JoinColumn({ name: 'workspaceId' })
|
||||
workspace: Relation<WorkspaceEntity> | null;
|
||||
|
||||
@Column({ type: 'uuid', nullable: true })
|
||||
workspaceId: string | null;
|
||||
|
||||
@CreateDateColumn({ type: 'timestamptz' })
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
@@ -2,10 +2,11 @@ import { Module } from '@nestjs/common';
|
||||
import { DiscoveryModule } from '@nestjs/core';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { UpgradeMigrationEntity } from 'src/engine/core-modules/upgrade/upgrade-migration.entity';
|
||||
import { InstanceUpgradeService } from 'src/engine/core-modules/upgrade/services/instance-upgrade.service';
|
||||
import { RegisteredInstanceMigrationService } from 'src/engine/core-modules/upgrade/services/registered-instance-migration-registry.service';
|
||||
import { UpgradeMigrationService } from 'src/engine/core-modules/upgrade/services/upgrade-migration.service';
|
||||
import { WorkspaceUpgradeService } from 'src/engine/core-modules/upgrade/services/workspace-upgrade.service';
|
||||
import { UpgradeMigrationEntity } from 'src/engine/core-modules/upgrade/upgrade-migration.entity';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
|
||||
@Module({
|
||||
@@ -14,11 +15,13 @@ import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.ent
|
||||
TypeOrmModule.forFeature([UpgradeMigrationEntity, WorkspaceEntity]),
|
||||
],
|
||||
providers: [
|
||||
UpgradeMigrationService,
|
||||
InstanceUpgradeService,
|
||||
WorkspaceUpgradeService,
|
||||
RegisteredInstanceMigrationService,
|
||||
],
|
||||
exports: [
|
||||
UpgradeMigrationService,
|
||||
InstanceUpgradeService,
|
||||
WorkspaceUpgradeService,
|
||||
RegisteredInstanceMigrationService,
|
||||
|
||||
Reference in New Issue
Block a user