Improve upgrade command and prepare 1.5 release (#14325)
In this PR: - refactor the upgrade command / upgrade command runner to keep upgrade command as light as possible (all wrapping logic should go to upgrade command runner) - prevent any upgrade if there is at least one workspace.version < previsousVersion ==> this leads to corrupted state where only core migrations are run if the self-hoster is skipping a version
This commit is contained in:
+10
-8
@@ -8,12 +8,14 @@ exports[`UpgradeCommandRunner Workspace upgrade should fail when current version
|
||||
|
||||
exports[`UpgradeCommandRunner Workspace upgrade should fail when previous version is not found 1`] = `[Error: No previous version found for version 1.0.0. Please review the "allCommands" record. Available versions are: 1.0.0, 2.0.0]`;
|
||||
|
||||
exports[`UpgradeCommandRunner Workspace upgrade should fail when workspace version is not defined 1`] = `[Error: WORKSPACE_VERSION_NOT_DEFINED workspace=workspace_0]`;
|
||||
exports[`UpgradeCommandRunner Workspace upgrade should fail when workspace version is not defined 1`] = `
|
||||
[Error: Unable to run the upgrade command. Aborting the upgrade process.
|
||||
Please ensure that all workspaces are on at least the previous minor version (1.0.0).
|
||||
If any workspaces are not on the previous minor version, roll back to that version and run the upgrade command again.]
|
||||
`;
|
||||
|
||||
exports[`UpgradeCommandRunner Workspace upgrade should fail when workspace version is not equal to fromVersion 1`] = `[Error: WORKSPACE_VERSION_MISSMATCH Upgrade for workspace workspace_0 failed as its version is beneath fromWorkspaceVersion=1.0.0]`;
|
||||
|
||||
exports[`UpgradeCommandRunner should run upgrade command with failing and successful workspaces 1`] = `[Error: WORKSPACE_VERSION_MISSMATCH Upgrade for workspace outated_version_workspace failed as its version is beneath fromWorkspaceVersion=1.0.0]`;
|
||||
|
||||
exports[`UpgradeCommandRunner should run upgrade command with failing and successful workspaces 2`] = `[Error: Received invalid version: invalid 1.0.0]`;
|
||||
|
||||
exports[`UpgradeCommandRunner should run upgrade command with failing and successful workspaces 3`] = `[Error: WORKSPACE_VERSION_NOT_DEFINED workspace=null_version_workspace]`;
|
||||
exports[`UpgradeCommandRunner Workspace upgrade should fail when workspace version is not equal to fromVersion 1`] = `
|
||||
[Error: Unable to run the upgrade command. Aborting the upgrade process.
|
||||
Please ensure that all workspaces are on at least the previous minor version (1.0.0).
|
||||
If any workspaces are not on the previous minor version, roll back to that version and run the upgrade command again.]
|
||||
`;
|
||||
|
||||
+43
-89
@@ -1,7 +1,10 @@
|
||||
import { Test, type TestingModule } from '@nestjs/testing';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
|
||||
import { type EachTestingContext } from 'twenty-shared/testing';
|
||||
import {
|
||||
eachTestingContextFilter,
|
||||
type EachTestingContext,
|
||||
} from 'twenty-shared/testing';
|
||||
import { type Repository } from 'typeorm';
|
||||
|
||||
import { UpgradeCommandRunner } from 'src/database/commands/command-runners/upgrade.command-runner';
|
||||
@@ -125,6 +128,7 @@ describe('UpgradeCommandRunner', () => {
|
||||
let syncWorkspaceMetadataCommand: jest.Mocked<SyncWorkspaceMetadataCommand>;
|
||||
let runAfterSyncMetadataSpy: jest.SpyInstance;
|
||||
let runBeforeSyncMetadataSpy: jest.SpyInstance;
|
||||
let runCoreMigrationsSpy: jest.SpyInstance;
|
||||
let twentyORMGlobalManagerSpy: TwentyORMGlobalManager;
|
||||
|
||||
type BuildModuleAndSetupSpiesArgs = {
|
||||
@@ -165,6 +169,9 @@ describe('UpgradeCommandRunner', () => {
|
||||
'runAfterSyncMetadata',
|
||||
);
|
||||
jest.spyOn(upgradeCommandRunner, 'runOnWorkspace');
|
||||
runCoreMigrationsSpy = jest
|
||||
.spyOn(upgradeCommandRunner, 'runCoreMigrations')
|
||||
.mockImplementation(() => Promise.resolve());
|
||||
|
||||
workspaceRepository = module.get<Repository<Workspace>>(
|
||||
getRepositoryToken(Workspace),
|
||||
@@ -213,76 +220,6 @@ describe('UpgradeCommandRunner', () => {
|
||||
].forEach((fn) => expect(fn).not.toHaveBeenCalled());
|
||||
});
|
||||
|
||||
it('should run upgrade command with failing and successful workspaces', async () => {
|
||||
const outdatedVersionWorkspaces = generateMockWorkspace({
|
||||
id: 'outated_version_workspace',
|
||||
version: '0.42.42',
|
||||
});
|
||||
const invalidVersionWorkspace = generateMockWorkspace({
|
||||
id: 'invalid_version_workspace',
|
||||
version: 'invalid',
|
||||
});
|
||||
const nullVersionWorkspace = generateMockWorkspace({
|
||||
id: 'null_version_workspace',
|
||||
version: null,
|
||||
});
|
||||
const numberOfValidWorkspace = 4;
|
||||
const failingWorkspaces = [
|
||||
outdatedVersionWorkspaces,
|
||||
invalidVersionWorkspace,
|
||||
nullVersionWorkspace,
|
||||
];
|
||||
const totalWorkspace = numberOfValidWorkspace + failingWorkspaces.length;
|
||||
const appVersion = 'v2.0.0';
|
||||
const expectedToVersion = '2.0.0';
|
||||
|
||||
await buildModuleAndSetupSpies({
|
||||
numberOfWorkspace: numberOfValidWorkspace,
|
||||
workspaces: failingWorkspaces,
|
||||
appVersion,
|
||||
});
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
const passedParams = [];
|
||||
const options = {};
|
||||
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
await upgradeCommandRunner.run(passedParams, options);
|
||||
|
||||
// Common assertions
|
||||
const { fail: failReport, success: successReport } =
|
||||
upgradeCommandRunner.migrationReport;
|
||||
|
||||
[
|
||||
twentyORMGlobalManagerSpy.destroyDataSourceForWorkspace,
|
||||
upgradeCommandRunner.runOnWorkspace,
|
||||
].forEach((fn) => expect(fn).toHaveBeenCalledTimes(totalWorkspace));
|
||||
expect(failReport.length + successReport.length).toBe(totalWorkspace);
|
||||
|
||||
// Success assertions
|
||||
[
|
||||
upgradeCommandRunner.runBeforeSyncMetadata,
|
||||
syncWorkspaceMetadataCommand.runOnWorkspace,
|
||||
upgradeCommandRunner.runAfterSyncMetadata,
|
||||
].forEach((fn) => expect(fn).toHaveBeenCalledTimes(numberOfValidWorkspace));
|
||||
expect(successReport.length).toBe(numberOfValidWorkspace);
|
||||
expect(workspaceRepository.update).toHaveBeenNthCalledWith(
|
||||
numberOfValidWorkspace,
|
||||
{ id: expect.any(String) },
|
||||
{ version: expectedToVersion },
|
||||
);
|
||||
|
||||
// Failing assertions
|
||||
expect(failReport.length).toBe(failingWorkspaces.length);
|
||||
failReport.forEach((report) => {
|
||||
expect(
|
||||
failingWorkspaces.some(
|
||||
(workspace) => workspace.id === report.workspaceId,
|
||||
),
|
||||
).toBe(true);
|
||||
expect(report.error).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
it('should run upgrade over several workspaces', async () => {
|
||||
const numberOfWorkspace = 42;
|
||||
const appVersion = '2.0.0';
|
||||
@@ -383,7 +320,7 @@ describe('UpgradeCommandRunner', () => {
|
||||
},
|
||||
];
|
||||
|
||||
it.each(successfulTestUseCases)(
|
||||
it.each(eachTestingContextFilter(successfulTestUseCases))(
|
||||
'$title',
|
||||
async ({ context: { input } }) => {
|
||||
await buildModuleAndSetupSpies(input);
|
||||
@@ -400,8 +337,9 @@ describe('UpgradeCommandRunner', () => {
|
||||
|
||||
expect(failReport.length).toBe(0);
|
||||
expect(successReport.length).toBe(1);
|
||||
expect(runAfterSyncMetadataSpy).toBeCalledTimes(1);
|
||||
expect(runBeforeSyncMetadataSpy).toBeCalledTimes(1);
|
||||
expect(runCoreMigrationsSpy).toHaveBeenCalledTimes(1);
|
||||
expect(runAfterSyncMetadataSpy).toHaveBeenCalledTimes(1);
|
||||
expect(runBeforeSyncMetadataSpy).toHaveBeenCalledTimes(1);
|
||||
const { workspaceId } = successReport[0];
|
||||
|
||||
expect(workspaceId).toBe('workspace_0');
|
||||
@@ -412,6 +350,9 @@ describe('UpgradeCommandRunner', () => {
|
||||
describe('Workspace upgrade should fail', () => {
|
||||
const failingTestUseCases: EachTestingContext<{
|
||||
input: Omit<BuildModuleAndSetupSpiesArgs, 'numberOfWorkspace'>;
|
||||
output?: {
|
||||
failReportWorkspaceId: string;
|
||||
};
|
||||
}>[] = [
|
||||
{
|
||||
title: 'when workspace version is not equal to fromVersion',
|
||||
@@ -422,6 +363,9 @@ describe('UpgradeCommandRunner', () => {
|
||||
version: '0.1.0',
|
||||
},
|
||||
},
|
||||
output: {
|
||||
failReportWorkspaceId: 'workspace_0',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -432,6 +376,9 @@ describe('UpgradeCommandRunner', () => {
|
||||
version: null,
|
||||
},
|
||||
},
|
||||
output: {
|
||||
failReportWorkspaceId: 'workspace_0',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -440,6 +387,9 @@ describe('UpgradeCommandRunner', () => {
|
||||
input: {
|
||||
appVersion: null,
|
||||
},
|
||||
output: {
|
||||
failReportWorkspaceId: 'global',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -448,6 +398,9 @@ describe('UpgradeCommandRunner', () => {
|
||||
input: {
|
||||
appVersion: '42.0.0',
|
||||
},
|
||||
output: {
|
||||
failReportWorkspaceId: 'global',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -468,25 +421,26 @@ describe('UpgradeCommandRunner', () => {
|
||||
},
|
||||
];
|
||||
|
||||
it.each(failingTestUseCases)('$title', async ({ context: { input } }) => {
|
||||
await buildModuleAndSetupSpies(input);
|
||||
it.each(eachTestingContextFilter(failingTestUseCases))(
|
||||
'$title',
|
||||
async ({ context: { input, output } }) => {
|
||||
await buildModuleAndSetupSpies(input);
|
||||
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
const passedParams = [];
|
||||
const options = {};
|
||||
const passedParams: string[] = [];
|
||||
const options = {};
|
||||
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
await upgradeCommandRunner.run(passedParams, options);
|
||||
await upgradeCommandRunner.run(passedParams, options);
|
||||
|
||||
const { fail: failReport, success: successReport } =
|
||||
upgradeCommandRunner.migrationReport;
|
||||
const { fail: failReport, success: successReport } =
|
||||
upgradeCommandRunner.migrationReport;
|
||||
|
||||
expect(successReport.length).toBe(0);
|
||||
expect(failReport.length).toBe(1);
|
||||
const { workspaceId, error } = failReport[0];
|
||||
expect(successReport.length).toBe(0);
|
||||
expect(failReport.length).toBe(1);
|
||||
const { workspaceId, error } = failReport[0];
|
||||
|
||||
expect(workspaceId).toBe('workspace_0');
|
||||
expect(error).toMatchSnapshot();
|
||||
});
|
||||
expect(workspaceId).toBe(output?.failReportWorkspaceId ?? 'global');
|
||||
expect(error).toMatchSnapshot();
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
+151
-3
@@ -1,11 +1,16 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { exec } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
|
||||
import chalk from 'chalk';
|
||||
import { SemVer } from 'semver';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
|
||||
import { In, Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
ActiveOrSuspendedWorkspacesMigrationCommandOptions,
|
||||
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
|
||||
type RunOnWorkspaceArgs,
|
||||
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
@@ -24,6 +29,8 @@ export type VersionCommands = {
|
||||
afterSyncMetadata: ActiveOrSuspendedWorkspacesMigrationCommandRunner[];
|
||||
};
|
||||
export type AllCommands = Record<string, VersionCommands>;
|
||||
const execPromise = promisify(exec);
|
||||
|
||||
export abstract class UpgradeCommandRunner extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
private fromWorkspaceVersion: SemVer;
|
||||
private currentAppVersion: SemVer;
|
||||
@@ -41,14 +48,99 @@ export abstract class UpgradeCommandRunner extends ActiveOrSuspendedWorkspacesMi
|
||||
super(workspaceRepository, twentyORMGlobalManager);
|
||||
}
|
||||
|
||||
private async loadActiveOrSuspendedWorkspace() {
|
||||
return await this.workspaceRepository.find({
|
||||
select: ['id', 'version', 'displayName'],
|
||||
where: {
|
||||
activationStatus: In([
|
||||
WorkspaceActivationStatus.ACTIVE,
|
||||
WorkspaceActivationStatus.SUSPENDED,
|
||||
]),
|
||||
},
|
||||
order: {
|
||||
id: 'ASC',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private async shouldSkipUpgradeIfFreshInstallation(): Promise<boolean> {
|
||||
const activeWorkspaceOrSuspendedWorkspaceCount =
|
||||
await this.loadActiveOrSuspendedWorkspace();
|
||||
|
||||
return activeWorkspaceOrSuspendedWorkspaceCount.length === 0;
|
||||
}
|
||||
|
||||
async runCoreMigrations(): Promise<void> {
|
||||
this.logger.log('Running global database migrations');
|
||||
|
||||
try {
|
||||
this.logger.log('Running core datasource migrations...');
|
||||
const coreResult = await execPromise(
|
||||
'npx -y typeorm migration:run -d dist/src/database/typeorm/core/core.datasource',
|
||||
);
|
||||
|
||||
this.logger.log(coreResult.stdout);
|
||||
|
||||
this.logger.log('Database migrations completed successfully');
|
||||
} catch (error) {
|
||||
this.logger.error('Error running database migrations:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private async workspacesThatAreBelowFromWorkspaceVersion(
|
||||
fromWorkspaceVersion: SemVer,
|
||||
): Promise<Pick<Workspace, 'id' | 'displayName' | 'version'>[]> {
|
||||
try {
|
||||
const allActiveOrSuspendedWorkspaces =
|
||||
await this.loadActiveOrSuspendedWorkspace();
|
||||
|
||||
if (allActiveOrSuspendedWorkspaces.length === 0) {
|
||||
this.logger.log(
|
||||
'No workspaces found. Running migrations for fresh installation.',
|
||||
);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
const workspacesThatAreBelowFromWorkspaceVersion =
|
||||
allActiveOrSuspendedWorkspaces.filter((workspace) => {
|
||||
if (!isDefined(workspace.version)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
const versionCompareResult = compareVersionMajorAndMinor(
|
||||
workspace.version,
|
||||
fromWorkspaceVersion.version,
|
||||
);
|
||||
|
||||
return versionCompareResult === 'lower';
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Error checking workspace ${workspace.id} version: ${error.message}`,
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
return workspacesThatAreBelowFromWorkspaceVersion;
|
||||
} catch (error) {
|
||||
this.logger.error('Error checking workspaces below version:', error);
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private setUpgradeContextVersionsAndCommandsForCurrentAppVersion() {
|
||||
const ugpradeContextIsAlreadyDefined = [
|
||||
const upgradeContextIsAlreadyDefined = [
|
||||
this.currentAppVersion,
|
||||
this.commands,
|
||||
this.fromWorkspaceVersion,
|
||||
].every(isDefined);
|
||||
|
||||
if (ugpradeContextIsAlreadyDefined) {
|
||||
if (upgradeContextIsAlreadyDefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -87,6 +179,62 @@ export abstract class UpgradeCommandRunner extends ActiveOrSuspendedWorkspacesMi
|
||||
this.logger.log(chalk.blue(message.join('\n ')));
|
||||
}
|
||||
|
||||
override async runMigrationCommand(
|
||||
passedParams: string[],
|
||||
options: ActiveOrSuspendedWorkspacesMigrationCommandOptions,
|
||||
): Promise<void> {
|
||||
try {
|
||||
this.setUpgradeContextVersionsAndCommandsForCurrentAppVersion();
|
||||
|
||||
const shouldSkipUpgradeIfFreshInstallation =
|
||||
await this.shouldSkipUpgradeIfFreshInstallation();
|
||||
|
||||
if (shouldSkipUpgradeIfFreshInstallation) {
|
||||
this.logger.log(
|
||||
chalk.blue('Fresh installation detected, skipping migration'),
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const workspacesThatAreBelowFromWorkspaceVersion =
|
||||
await this.workspacesThatAreBelowFromWorkspaceVersion(
|
||||
this.fromWorkspaceVersion,
|
||||
);
|
||||
|
||||
if (workspacesThatAreBelowFromWorkspaceVersion.length > 0) {
|
||||
this.migrationReport.fail.push(
|
||||
...workspacesThatAreBelowFromWorkspaceVersion.map((workspace) => ({
|
||||
error: new Error(
|
||||
`Unable to run the upgrade command. Aborting the upgrade process.
|
||||
Please ensure that all workspaces are on at least the previous minor version (${this.fromWorkspaceVersion.version}).
|
||||
If any workspaces are not on the previous minor version, roll back to that version and run the upgrade command again.`,
|
||||
),
|
||||
workspaceId: workspace.id,
|
||||
})),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
this.migrationReport.fail.push({
|
||||
error,
|
||||
workspaceId: 'global',
|
||||
});
|
||||
}
|
||||
|
||||
if (this.migrationReport.fail.length > 0) {
|
||||
this.migrationReport.fail.forEach(({ error, workspaceId }) =>
|
||||
this.logger.error(
|
||||
`Error in workspace ${workspaceId}: ${error.message}`,
|
||||
),
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await this.runCoreMigrations();
|
||||
await super.runMigrationCommand(passedParams, options);
|
||||
}
|
||||
|
||||
override async runOnWorkspace(args: RunOnWorkspaceArgs): Promise<void> {
|
||||
this.setUpgradeContextVersionsAndCommandsForCurrentAppVersion();
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { CronRegisterAllCommand } from 'src/database/commands/cron-register-all.command';
|
||||
import { ConfirmationQuestion } from 'src/database/commands/questions/confirmation.question';
|
||||
import { UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/upgrade-version-command.module';
|
||||
import { MigrateViewsToCoreCommand } from 'src/database/commands/views-migration/migrate-views-to-core.command';
|
||||
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
|
||||
import { ApiKeyModule } from 'src/engine/core-modules/api-key/api-key.module';
|
||||
import { DomainManagerModule } from 'src/engine/core-modules/domain-manager/domain-manager.module';
|
||||
@@ -50,7 +49,6 @@ import { DataSeedWorkspaceCommand } from './data-seed-dev-workspace.command';
|
||||
],
|
||||
providers: [
|
||||
DataSeedWorkspaceCommand,
|
||||
MigrateViewsToCoreCommand,
|
||||
ConfirmationQuestion,
|
||||
CronRegisterAllCommand,
|
||||
],
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ export type AddPositionsToWorkflowVersionsAndWorkflowRunsOptions =
|
||||
name: 'upgrade:1-5:add-positions-to-workflow-versions-and-workflow-runs',
|
||||
description: 'Add positions to workflow versions and workflow runs',
|
||||
})
|
||||
export class AddPositionsToWorkflowVersionsAndWorkflowRuns extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
export class AddPositionsToWorkflowVersionsAndWorkflowRunsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
constructor(
|
||||
@InjectRepository(Workspace)
|
||||
protected readonly workspaceRepository: Repository<Workspace>,
|
||||
|
||||
+2
-5
@@ -8,7 +8,6 @@ import {
|
||||
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
|
||||
type 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 { ViewFieldEntity } from 'src/engine/core-modules/view/entities/view-field.entity';
|
||||
import { ViewFilterGroupEntity } from 'src/engine/core-modules/view/entities/view-filter-group.entity';
|
||||
import { ViewFilterEntity } from 'src/engine/core-modules/view/entities/view-filter.entity';
|
||||
@@ -34,15 +33,13 @@ import { convertViewFilterOperandToCoreOperand } from 'src/modules/view/utils/co
|
||||
import { convertViewFilterWorkspaceValueToCoreValue } from 'src/modules/view/utils/convert-view-filter-workspace-value-to-core-value';
|
||||
|
||||
@Command({
|
||||
name: 'migrate:views-to-core',
|
||||
description:
|
||||
'Migrate views from workspace schemas to core schema and enable IS_CORE_VIEW_SYNCING_ENABLED feature flag',
|
||||
name: 'upgrade:1-5:migrate-views-to-core',
|
||||
description: 'Migrate views from workspace schemas to core schema',
|
||||
})
|
||||
export class MigrateViewsToCoreCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
constructor(
|
||||
@InjectRepository(Workspace)
|
||||
protected readonly workspaceRepository: Repository<Workspace>,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
@InjectDataSource()
|
||||
private readonly coreDataSource: DataSource,
|
||||
+2
-2
@@ -22,10 +22,10 @@ import {
|
||||
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
|
||||
|
||||
@Command({
|
||||
name: 'migrate:1-5:remove-favorite-view-relation',
|
||||
name: 'upgrade:1-5:remove-favorite-view-relation',
|
||||
description: 'Remove favorite view relation.',
|
||||
})
|
||||
export class RemoveFavoriteViewRelation extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
export class RemoveFavoriteViewRelationCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
constructor(
|
||||
@InjectRepository(Workspace)
|
||||
protected readonly workspaceRepository: Repository<Workspace>,
|
||||
|
||||
+9
-6
@@ -1,8 +1,9 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { AddPositionsToWorkflowVersionsAndWorkflowRuns } from 'src/database/commands/upgrade-version-command/1-5/1-5-add-positions-to-workflow-versions-and-workflow-runs.command';
|
||||
import { RemoveFavoriteViewRelation } from 'src/database/commands/upgrade-version-command/1-5/1-5-remove-favorite-view-relation.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 { 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';
|
||||
@@ -22,12 +23,14 @@ import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/works
|
||||
WorkspaceMetadataVersionModule,
|
||||
],
|
||||
providers: [
|
||||
RemoveFavoriteViewRelation,
|
||||
AddPositionsToWorkflowVersionsAndWorkflowRuns,
|
||||
RemoveFavoriteViewRelationCommand,
|
||||
AddPositionsToWorkflowVersionsAndWorkflowRunsCommand,
|
||||
MigrateViewsToCoreCommand,
|
||||
],
|
||||
exports: [
|
||||
RemoveFavoriteViewRelation,
|
||||
AddPositionsToWorkflowVersionsAndWorkflowRuns,
|
||||
RemoveFavoriteViewRelationCommand,
|
||||
AddPositionsToWorkflowVersionsAndWorkflowRunsCommand,
|
||||
MigrateViewsToCoreCommand,
|
||||
],
|
||||
})
|
||||
export class V1_5_UpgradeVersionCommandModule {}
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { 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([
|
||||
Workspace,
|
||||
FieldMetadataEntity,
|
||||
ObjectMetadataEntity,
|
||||
]),
|
||||
WorkspaceDataSourceModule,
|
||||
WorkspaceSchemaManagerModule,
|
||||
WorkspaceMetadataVersionModule,
|
||||
],
|
||||
providers: [],
|
||||
exports: [],
|
||||
})
|
||||
export class V1_6_UpgradeVersionCommandModule {}
|
||||
+4
-5
@@ -7,10 +7,8 @@ import { V1_1_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-
|
||||
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 {
|
||||
DatabaseMigrationService,
|
||||
UpgradeCommand,
|
||||
} from 'src/database/commands/upgrade-version-command/upgrade.command';
|
||||
import { V1_6_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-6/1-6-upgrade-version-command.module';
|
||||
import { UpgradeCommand } from 'src/database/commands/upgrade-version-command/upgrade.command';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.module';
|
||||
|
||||
@@ -23,8 +21,9 @@ import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/worksp
|
||||
V1_2_UpgradeVersionCommandModule,
|
||||
V1_3_UpgradeVersionCommandModule,
|
||||
V1_5_UpgradeVersionCommandModule,
|
||||
V1_6_UpgradeVersionCommandModule,
|
||||
WorkspaceSyncMetadataModule,
|
||||
],
|
||||
providers: [DatabaseMigrationService, UpgradeCommand],
|
||||
providers: [UpgradeCommand],
|
||||
})
|
||||
export class UpgradeVersionCommandModule {}
|
||||
|
||||
+17
-125
@@ -1,13 +1,7 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { exec } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
|
||||
import chalk from 'chalk';
|
||||
import { Command } from 'nest-commander';
|
||||
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
|
||||
import { In, Repository } from 'typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { type ActiveOrSuspendedWorkspacesMigrationCommandOptions } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import {
|
||||
@@ -31,96 +25,13 @@ import { RemoveWorkflowRunsWithoutState } from 'src/database/commands/upgrade-ve
|
||||
import { AddNextStepIdsToWorkflowRunsTrigger } from 'src/database/commands/upgrade-version-command/1-3/1-3-add-next-step-ids-to-workflow-runs-trigger.command';
|
||||
import { AssignRolesToExistingApiKeysCommand } from 'src/database/commands/upgrade-version-command/1-3/1-3-assign-roles-to-existing-api-keys.command';
|
||||
import { UpdateTimestampColumnTypeInWorkspaceSchemaCommand } from 'src/database/commands/upgrade-version-command/1-3/1-3-update-timestamp-column-type-in-workspace-schema.command';
|
||||
import { AddPositionsToWorkflowVersionsAndWorkflowRuns } from 'src/database/commands/upgrade-version-command/1-5/1-5-add-positions-to-workflow-versions-and-workflow-runs.command';
|
||||
import { RemoveFavoriteViewRelation } from 'src/database/commands/upgrade-version-command/1-5/1-5-remove-favorite-view-relation.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 { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { SyncWorkspaceMetadataCommand } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/sync-workspace-metadata.command';
|
||||
import { compareVersionMajorAndMinor } from 'src/utils/version/compare-version-minor-and-major';
|
||||
|
||||
const execPromise = promisify(exec);
|
||||
|
||||
@Injectable()
|
||||
export class DatabaseMigrationService {
|
||||
private logger = new Logger(DatabaseMigrationService.name);
|
||||
|
||||
constructor(
|
||||
@InjectRepository(Workspace)
|
||||
private readonly workspaceRepository: Repository<Workspace>,
|
||||
) {}
|
||||
|
||||
// TODO centralize with ActiveOrSuspendedRunner method
|
||||
private async loadActiveOrSuspendedWorkspace() {
|
||||
return await this.workspaceRepository.find({
|
||||
select: ['id', 'version'],
|
||||
where: {
|
||||
activationStatus: In([
|
||||
WorkspaceActivationStatus.ACTIVE,
|
||||
WorkspaceActivationStatus.SUSPENDED,
|
||||
]),
|
||||
},
|
||||
order: {
|
||||
id: 'ASC',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async shouldSkipUpgradeIfFreshInstallation(): Promise<boolean> {
|
||||
const activeWorkspaceOrSuspendedWorkspaceCount =
|
||||
await this.loadActiveOrSuspendedWorkspace();
|
||||
|
||||
return activeWorkspaceOrSuspendedWorkspaceCount.length === 0;
|
||||
}
|
||||
|
||||
async runMigrations(): Promise<void> {
|
||||
this.logger.log('Running global database migrations');
|
||||
|
||||
try {
|
||||
this.logger.log('Running core datasource migrations...');
|
||||
const coreResult = await execPromise(
|
||||
'npx -y typeorm migration:run -d dist/src/database/typeorm/core/core.datasource',
|
||||
);
|
||||
|
||||
this.logger.log(coreResult.stdout);
|
||||
|
||||
this.logger.log('Database migrations completed successfully');
|
||||
} catch (error) {
|
||||
this.logger.error('Error running database migrations:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
public async areAllWorkspacesAboveVersion0_53(): Promise<boolean> {
|
||||
try {
|
||||
const allActiveOrSuspendedWorkspaces =
|
||||
await this.loadActiveOrSuspendedWorkspace();
|
||||
|
||||
if (allActiveOrSuspendedWorkspaces.length === 0) {
|
||||
this.logger.log(
|
||||
'No workspaces found. Running migrations for fresh installation.',
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const workspacesBelowVersion = allActiveOrSuspendedWorkspaces.filter(
|
||||
({ version }) =>
|
||||
version === null ||
|
||||
compareVersionMajorAndMinor(version, '0.53.0') === 'lower',
|
||||
);
|
||||
|
||||
this.logger.log(
|
||||
`Found ${workspacesBelowVersion.length} active or suspended workspaces that are below version 0.53.0 \n${workspacesBelowVersion.map((el) => el.id).join('\n')}`,
|
||||
);
|
||||
|
||||
return workspacesBelowVersion.length === 0;
|
||||
} catch (error) {
|
||||
this.logger.error('Error checking workspaces below version:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Command({
|
||||
name: 'upgrade',
|
||||
@@ -136,8 +47,6 @@ export class UpgradeCommand extends UpgradeCommandRunner {
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
protected readonly syncWorkspaceMetadataCommand: SyncWorkspaceMetadataCommand,
|
||||
|
||||
private readonly databaseMigrationService: DatabaseMigrationService,
|
||||
|
||||
// 0.54 Commands
|
||||
protected readonly fixStandardSelectFieldsPositionCommand: FixStandardSelectFieldsPositionCommand,
|
||||
protected readonly fixCreatedByDefaultValueCommand: FixCreatedByDefaultValueCommand,
|
||||
@@ -165,8 +74,9 @@ export class UpgradeCommand extends UpgradeCommandRunner {
|
||||
protected readonly updateTimestampColumnTypeInWorkspaceSchemaCommand: UpdateTimestampColumnTypeInWorkspaceSchemaCommand,
|
||||
|
||||
// 1.5 Commands
|
||||
protected readonly removeFavoriteViewRelation: RemoveFavoriteViewRelation,
|
||||
protected readonly addPositionsToWorkflowVersionsAndWorkflowRuns: AddPositionsToWorkflowVersionsAndWorkflowRuns,
|
||||
protected readonly removeFavoriteViewRelationCommand: RemoveFavoriteViewRelationCommand,
|
||||
protected readonly addPositionsToWorkflowVersionsAndWorkflowRunsCommand: AddPositionsToWorkflowVersionsAndWorkflowRunsCommand,
|
||||
protected readonly migrateViewsToCoreCommand: MigrateViewsToCoreCommand,
|
||||
) {
|
||||
super(
|
||||
workspaceRepository,
|
||||
@@ -242,12 +152,18 @@ export class UpgradeCommand extends UpgradeCommandRunner {
|
||||
|
||||
const commands_150: VersionCommands = {
|
||||
beforeSyncMetadata: [
|
||||
this.removeFavoriteViewRelation,
|
||||
this.addPositionsToWorkflowVersionsAndWorkflowRuns,
|
||||
this.migrateViewsToCoreCommand,
|
||||
this.removeFavoriteViewRelationCommand,
|
||||
this.addPositionsToWorkflowVersionsAndWorkflowRunsCommand,
|
||||
],
|
||||
afterSyncMetadata: [],
|
||||
};
|
||||
|
||||
const commands_160: VersionCommands = {
|
||||
beforeSyncMetadata: [],
|
||||
afterSyncMetadata: [],
|
||||
};
|
||||
|
||||
this.allCommands = {
|
||||
'0.53.0': commands_053,
|
||||
'0.54.0': commands_054,
|
||||
@@ -259,6 +175,7 @@ export class UpgradeCommand extends UpgradeCommandRunner {
|
||||
'1.3.0': commands_130,
|
||||
'1.4.0': commands_140,
|
||||
'1.5.0': commands_150,
|
||||
'1.6.0': commands_160,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -266,31 +183,6 @@ export class UpgradeCommand extends UpgradeCommandRunner {
|
||||
passedParams: string[],
|
||||
options: ActiveOrSuspendedWorkspacesMigrationCommandOptions,
|
||||
): Promise<void> {
|
||||
const shouldSkipUpgradeIfFreshInstallation =
|
||||
await this.databaseMigrationService.shouldSkipUpgradeIfFreshInstallation();
|
||||
|
||||
if (shouldSkipUpgradeIfFreshInstallation) {
|
||||
this.logger.log(
|
||||
chalk.blue('Fresh installation detected, skipping migration'),
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const shouldPreventFromUpgradingIfWorkspaceIsBelowVersion0_53 =
|
||||
!(await this.databaseMigrationService.areAllWorkspacesAboveVersion0_53());
|
||||
|
||||
if (shouldPreventFromUpgradingIfWorkspaceIsBelowVersion0_53) {
|
||||
this.logger.log(
|
||||
chalk.red(
|
||||
'Not able to run migrate command, aborting the whole migrate-upgrade operation',
|
||||
),
|
||||
);
|
||||
throw new Error('Could not run migration aborting');
|
||||
}
|
||||
|
||||
await this.databaseMigrationService.runMigrations();
|
||||
|
||||
await super.runMigrationCommand(passedParams, options);
|
||||
return await super.runMigrationCommand(passedParams, options);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user