Provide a wrapper to execute command on workspace with easier devXP (#10391)
Proposal: - Add a method in ActiveWorkspaceCommand to loop over workspace safely (add counter, add try / catch, provide datasource with fresh cache, destroy datasource => as we do always do it) Also in this PR: - make sure we clear all dataSources (and not only the one on metadata version in RAM)
This commit is contained in:
@ -8,6 +8,8 @@ import {
|
|||||||
BaseCommandRunner,
|
BaseCommandRunner,
|
||||||
} from 'src/database/commands/base.command';
|
} from 'src/database/commands/base.command';
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
|
import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
|
||||||
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
export type ActiveWorkspacesCommandOptions = BaseCommandOptions & {
|
export type ActiveWorkspacesCommandOptions = BaseCommandOptions & {
|
||||||
workspaceId?: string;
|
workspaceId?: string;
|
||||||
startFromWorkspaceId?: string;
|
startFromWorkspaceId?: string;
|
||||||
@ -19,7 +21,10 @@ export abstract class ActiveWorkspacesCommandRunner extends BaseCommandRunner {
|
|||||||
private startFromWorkspaceId: string | undefined;
|
private startFromWorkspaceId: string | undefined;
|
||||||
private workspaceCountLimit: number | undefined;
|
private workspaceCountLimit: number | undefined;
|
||||||
|
|
||||||
constructor(protected readonly workspaceRepository: Repository<Workspace>) {
|
constructor(
|
||||||
|
protected readonly workspaceRepository: Repository<Workspace>,
|
||||||
|
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,6 +127,54 @@ export abstract class ActiveWorkspacesCommandRunner extends BaseCommandRunner {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected async processEachWorkspaceWithWorkspaceDataSource(
|
||||||
|
workspaceIds: string[],
|
||||||
|
callback: ({
|
||||||
|
workspaceId,
|
||||||
|
index,
|
||||||
|
total,
|
||||||
|
dataSource,
|
||||||
|
}: {
|
||||||
|
workspaceId: string;
|
||||||
|
index: number;
|
||||||
|
total: number;
|
||||||
|
dataSource: WorkspaceDataSource;
|
||||||
|
}) => Promise<void>,
|
||||||
|
): Promise<void> {
|
||||||
|
this.logger.log(
|
||||||
|
chalk.green(`Running command on ${workspaceIds.length} workspaces`),
|
||||||
|
);
|
||||||
|
for (const [index, workspaceId] of workspaceIds.entries()) {
|
||||||
|
this.logger.log(
|
||||||
|
chalk.green(
|
||||||
|
`Processing workspace ${workspaceId} (${index + 1}/${
|
||||||
|
workspaceIds.length
|
||||||
|
})`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const dataSource =
|
||||||
|
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
|
||||||
|
workspaceId,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await callback({
|
||||||
|
workspaceId,
|
||||||
|
index,
|
||||||
|
total: workspaceIds.length,
|
||||||
|
dataSource,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(`Error in workspace ${workspaceId}: ${error}`);
|
||||||
|
}
|
||||||
|
await this.twentyORMGlobalManager.destroyDataSourceForWorkspace(
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected abstract executeActiveWorkspacesCommand(
|
protected abstract executeActiveWorkspacesCommand(
|
||||||
passedParams: string[],
|
passedParams: string[],
|
||||||
options: BaseCommandOptions,
|
options: BaseCommandOptions,
|
||||||
|
|||||||
@ -7,8 +7,6 @@ import { DataSeedDemoWorkspaceCommand } from 'src/database/commands/data-seed-de
|
|||||||
import { DataSeedDemoWorkspaceModule } from 'src/database/commands/data-seed-demo-workspace/data-seed-demo-workspace.module';
|
import { DataSeedDemoWorkspaceModule } from 'src/database/commands/data-seed-demo-workspace/data-seed-demo-workspace.module';
|
||||||
import { DataSeedWorkspaceCommand } from 'src/database/commands/data-seed-dev-workspace.command';
|
import { DataSeedWorkspaceCommand } from 'src/database/commands/data-seed-dev-workspace.command';
|
||||||
import { ConfirmationQuestion } from 'src/database/commands/questions/confirmation.question';
|
import { ConfirmationQuestion } from 'src/database/commands/questions/confirmation.question';
|
||||||
import { UpgradeTo0_40CommandModule } from 'src/database/commands/upgrade-version/0-40/0-40-upgrade-version.module';
|
|
||||||
import { UpgradeTo0_41CommandModule } from 'src/database/commands/upgrade-version/0-41/0-41-upgrade-version.module';
|
|
||||||
import { UpgradeTo0_42CommandModule } from 'src/database/commands/upgrade-version/0-42/0-42-upgrade-version.module';
|
import { UpgradeTo0_42CommandModule } from 'src/database/commands/upgrade-version/0-42/0-42-upgrade-version.module';
|
||||||
import { UpgradeTo0_43CommandModule } from 'src/database/commands/upgrade-version/0-43/0-43-upgrade-version.module';
|
import { UpgradeTo0_43CommandModule } from 'src/database/commands/upgrade-version/0-43/0-43-upgrade-version.module';
|
||||||
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
|
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
|
||||||
@ -51,8 +49,6 @@ import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/worksp
|
|||||||
DataSeedDemoWorkspaceModule,
|
DataSeedDemoWorkspaceModule,
|
||||||
WorkspaceCacheStorageModule,
|
WorkspaceCacheStorageModule,
|
||||||
WorkspaceMetadataVersionModule,
|
WorkspaceMetadataVersionModule,
|
||||||
UpgradeTo0_40CommandModule,
|
|
||||||
UpgradeTo0_41CommandModule,
|
|
||||||
UpgradeTo0_42CommandModule,
|
UpgradeTo0_42CommandModule,
|
||||||
UpgradeTo0_43CommandModule,
|
UpgradeTo0_43CommandModule,
|
||||||
FeatureFlagModule,
|
FeatureFlagModule,
|
||||||
|
|||||||
@ -1,245 +0,0 @@
|
|||||||
import { InjectRepository } from '@nestjs/typeorm';
|
|
||||||
|
|
||||||
import chalk from 'chalk';
|
|
||||||
import { Command } from 'nest-commander';
|
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
import { v4 } from 'uuid';
|
|
||||||
import { isDefined } from 'twenty-shared';
|
|
||||||
|
|
||||||
import {
|
|
||||||
ActiveWorkspacesCommandOptions,
|
|
||||||
ActiveWorkspacesCommandRunner,
|
|
||||||
} from 'src/database/commands/active-workspaces.command';
|
|
||||||
import { isCommandLogger } from 'src/database/commands/logger';
|
|
||||||
import { AGGREGATE_OPERATIONS } 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 { 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 {
|
|
||||||
VIEW_FIELD_STANDARD_FIELD_IDS,
|
|
||||||
VIEW_STANDARD_FIELD_IDS,
|
|
||||||
} from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
|
|
||||||
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
|
|
||||||
|
|
||||||
@Command({
|
|
||||||
name: 'upgrade-0.40:migrate-aggregate-operation-options',
|
|
||||||
description: 'Add aggregate operations options to relevant fields',
|
|
||||||
})
|
|
||||||
export class MigrateAggregateOperationOptionsCommand extends ActiveWorkspacesCommandRunner {
|
|
||||||
constructor(
|
|
||||||
@InjectRepository(Workspace, 'core')
|
|
||||||
protected readonly workspaceRepository: Repository<Workspace>,
|
|
||||||
@InjectRepository(FieldMetadataEntity, 'metadata')
|
|
||||||
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
|
||||||
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
|
||||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
|
||||||
private readonly workspaceMigrationService: WorkspaceMigrationService,
|
|
||||||
private readonly workspaceMigrationFactory: WorkspaceMigrationFactory,
|
|
||||||
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
|
|
||||||
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
|
|
||||||
) {
|
|
||||||
super(workspaceRepository);
|
|
||||||
}
|
|
||||||
|
|
||||||
ADDITIONAL_AGGREGATE_OPERATIONS = [
|
|
||||||
{
|
|
||||||
value: AGGREGATE_OPERATIONS.countEmpty,
|
|
||||||
label: 'Count empty',
|
|
||||||
position: 5,
|
|
||||||
color: 'red',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: AGGREGATE_OPERATIONS.countNotEmpty,
|
|
||||||
label: 'Count not empty',
|
|
||||||
position: 6,
|
|
||||||
color: 'purple',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: AGGREGATE_OPERATIONS.countUniqueValues,
|
|
||||||
label: 'Count unique values',
|
|
||||||
position: 7,
|
|
||||||
color: 'sky',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: AGGREGATE_OPERATIONS.percentageEmpty,
|
|
||||||
label: 'Percent empty',
|
|
||||||
position: 8,
|
|
||||||
color: 'turquoise',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: AGGREGATE_OPERATIONS.percentageNotEmpty,
|
|
||||||
label: 'Percent not empty',
|
|
||||||
position: 9,
|
|
||||||
color: 'yellow',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
ADDITIONAL_AGGREGATE_OPERATIONS_VALUES =
|
|
||||||
this.ADDITIONAL_AGGREGATE_OPERATIONS.map((option) => option.value);
|
|
||||||
|
|
||||||
async executeActiveWorkspacesCommand(
|
|
||||||
_passedParam: string[],
|
|
||||||
options: ActiveWorkspacesCommandOptions,
|
|
||||||
workspaceIds: string[],
|
|
||||||
): Promise<void> {
|
|
||||||
this.logger.log(
|
|
||||||
'Running command to migrate aggregate operations options to include count operations',
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isCommandLogger(this.logger)) {
|
|
||||||
this.logger.setVerbose(options.verbose ?? false);
|
|
||||||
}
|
|
||||||
|
|
||||||
let workspaceIterator = 1;
|
|
||||||
|
|
||||||
for (const workspaceId of workspaceIds) {
|
|
||||||
this.logger.log(
|
|
||||||
`Running command for workspace ${workspaceId} ${workspaceIterator}/${workspaceIds.length}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const viewFieldObjectMetadata =
|
|
||||||
await this.objectMetadataRepository.findOne({
|
|
||||||
where: {
|
|
||||||
workspaceId,
|
|
||||||
standardId: STANDARD_OBJECT_IDS.viewField,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!isDefined(viewFieldObjectMetadata)) {
|
|
||||||
throw new Error(
|
|
||||||
`View field object metadata not found for workspace ${workspaceId}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const viewFieldAggregateOperationFieldMetadata =
|
|
||||||
await this.fieldMetadataRepository.findOne({
|
|
||||||
where: {
|
|
||||||
workspaceId,
|
|
||||||
objectMetadataId: viewFieldObjectMetadata.id,
|
|
||||||
standardId: VIEW_FIELD_STANDARD_FIELD_IDS.aggregateOperation,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isDefined(viewFieldAggregateOperationFieldMetadata)) {
|
|
||||||
await this.updateAggregateOperationField(
|
|
||||||
workspaceId,
|
|
||||||
viewFieldAggregateOperationFieldMetadata,
|
|
||||||
viewFieldObjectMetadata,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const viewObjectMetadata = await this.objectMetadataRepository.findOne({
|
|
||||||
where: {
|
|
||||||
workspaceId,
|
|
||||||
standardId: STANDARD_OBJECT_IDS.view,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!isDefined(viewObjectMetadata)) {
|
|
||||||
throw new Error(
|
|
||||||
`View object metadata not found for workspace ${workspaceId}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const viewAggregateOperationFieldMetadata =
|
|
||||||
await this.fieldMetadataRepository.findOne({
|
|
||||||
where: {
|
|
||||||
workspaceId,
|
|
||||||
objectMetadataId: viewObjectMetadata.id,
|
|
||||||
standardId: VIEW_STANDARD_FIELD_IDS.kanbanAggregateOperation,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isDefined(viewAggregateOperationFieldMetadata)) {
|
|
||||||
await this.updateAggregateOperationField(
|
|
||||||
workspaceId,
|
|
||||||
viewAggregateOperationFieldMetadata,
|
|
||||||
viewObjectMetadata,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
isDefined(viewAggregateOperationFieldMetadata) ||
|
|
||||||
isDefined(viewFieldAggregateOperationFieldMetadata)
|
|
||||||
) {
|
|
||||||
await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations(
|
|
||||||
workspaceId,
|
|
||||||
);
|
|
||||||
|
|
||||||
await this.workspaceMetadataVersionService.incrementMetadataVersion(
|
|
||||||
workspaceId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
workspaceIterator++;
|
|
||||||
this.logger.log(
|
|
||||||
chalk.green(`Command completed for workspace ${workspaceId}.`),
|
|
||||||
);
|
|
||||||
} catch {
|
|
||||||
this.logger.log(chalk.red(`Error in workspace ${workspaceId}.`));
|
|
||||||
workspaceIterator++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.logger.log(chalk.green(`Command completed!`));
|
|
||||||
}
|
|
||||||
|
|
||||||
private async updateAggregateOperationField(
|
|
||||||
workspaceId: string,
|
|
||||||
fieldMetadata: FieldMetadataEntity,
|
|
||||||
objectMetadata: ObjectMetadataEntity,
|
|
||||||
) {
|
|
||||||
if (
|
|
||||||
fieldMetadata.options.some((option) => {
|
|
||||||
return this.ADDITIONAL_AGGREGATE_OPERATIONS_VALUES.includes(
|
|
||||||
option.value as AGGREGATE_OPERATIONS,
|
|
||||||
);
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
this.logger.log(
|
|
||||||
`Aggregate operation field metadata ${fieldMetadata.name} already has the required options`,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
const updatedFieldMetadata = {
|
|
||||||
...fieldMetadata,
|
|
||||||
options: [
|
|
||||||
...fieldMetadata.options,
|
|
||||||
...this.ADDITIONAL_AGGREGATE_OPERATIONS.map((operation) => ({
|
|
||||||
...operation,
|
|
||||||
id: v4(),
|
|
||||||
})),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
await this.fieldMetadataRepository.save(updatedFieldMetadata);
|
|
||||||
|
|
||||||
await this.workspaceMigrationService.createCustomMigration(
|
|
||||||
generateMigrationName(
|
|
||||||
`update-${objectMetadata.nameSingular}-aggregate-operation`,
|
|
||||||
),
|
|
||||||
workspaceId,
|
|
||||||
[
|
|
||||||
{
|
|
||||||
name: computeObjectTargetTable(objectMetadata),
|
|
||||||
action: WorkspaceMigrationTableActionType.ALTER,
|
|
||||||
columns: this.workspaceMigrationFactory.createColumnActions(
|
|
||||||
WorkspaceMigrationColumnActionType.ALTER,
|
|
||||||
fieldMetadata,
|
|
||||||
updatedFieldMetadata,
|
|
||||||
),
|
|
||||||
} satisfies WorkspaceMigrationTableAction,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,248 +0,0 @@
|
|||||||
import { InjectRepository } from '@nestjs/typeorm';
|
|
||||||
|
|
||||||
import chalk from 'chalk';
|
|
||||||
import { Command, Option } from 'nest-commander';
|
|
||||||
import { WorkspaceActivationStatus } from 'twenty-shared';
|
|
||||||
import { In, Repository } from 'typeorm';
|
|
||||||
|
|
||||||
import {
|
|
||||||
BaseCommandOptions,
|
|
||||||
BaseCommandRunner,
|
|
||||||
} from 'src/database/commands/base.command';
|
|
||||||
import { rawDataSource } from 'src/database/typeorm/raw/raw.datasource';
|
|
||||||
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
|
|
||||||
import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity';
|
|
||||||
import { SubscriptionStatus } from 'src/engine/core-modules/billing/enums/billing-subscription-status.enum';
|
|
||||||
import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
|
||||||
import { KeyValuePair } from 'src/engine/core-modules/key-value-pair/key-value-pair.entity';
|
|
||||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
|
||||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
|
||||||
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
|
|
||||||
import { WorkspaceMigrationEntity } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
|
|
||||||
|
|
||||||
type UpdateInactiveWorkspaceStatusOptions = BaseCommandOptions & {
|
|
||||||
workspaceIds: string[];
|
|
||||||
};
|
|
||||||
|
|
||||||
@Command({
|
|
||||||
name: 'upgrade-0.40:update-inactive-workspace-status',
|
|
||||||
description:
|
|
||||||
'Update the status of inactive workspaces to SUSPENDED and delete them',
|
|
||||||
})
|
|
||||||
export class UpdateInactiveWorkspaceStatusCommand extends BaseCommandRunner {
|
|
||||||
constructor(
|
|
||||||
@InjectRepository(Workspace, 'core')
|
|
||||||
protected readonly workspaceRepository: Repository<Workspace>,
|
|
||||||
@InjectRepository(DataSourceEntity, 'metadata')
|
|
||||||
protected readonly datasourceRepository: Repository<DataSourceEntity>,
|
|
||||||
@InjectRepository(WorkspaceMigrationEntity, 'metadata')
|
|
||||||
protected readonly workspaceMigrationRepository: Repository<WorkspaceMigrationEntity>,
|
|
||||||
@InjectRepository(BillingSubscription, 'core')
|
|
||||||
protected readonly subscriptionRepository: Repository<BillingSubscription>,
|
|
||||||
@InjectRepository(FeatureFlag, 'core')
|
|
||||||
protected readonly featureFlagRepository: Repository<FeatureFlag>,
|
|
||||||
@InjectRepository(KeyValuePair, 'core')
|
|
||||||
protected readonly keyValuePairRepository: Repository<KeyValuePair>,
|
|
||||||
@InjectRepository(UserWorkspace, 'core')
|
|
||||||
protected readonly userWorkspaceRepository: Repository<UserWorkspace>,
|
|
||||||
@InjectRepository(User, 'core')
|
|
||||||
protected readonly userRepository: Repository<User>,
|
|
||||||
private readonly typeORMService: TypeORMService,
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Option({
|
|
||||||
flags: '-w, --workspace-ids [workspaceIds]',
|
|
||||||
description: 'Workspace ids to process (comma separated)',
|
|
||||||
})
|
|
||||||
parseWorkspaceIds(val: string): string[] {
|
|
||||||
return val.split(',');
|
|
||||||
}
|
|
||||||
|
|
||||||
override async executeBaseCommand(
|
|
||||||
_passedParams: string[],
|
|
||||||
options: UpdateInactiveWorkspaceStatusOptions,
|
|
||||||
): Promise<void> {
|
|
||||||
const whereCondition: any = {
|
|
||||||
activationStatus: WorkspaceActivationStatus.INACTIVE,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (options.workspaceIds?.length > 0) {
|
|
||||||
whereCondition.id = In(options.workspaceIds);
|
|
||||||
}
|
|
||||||
|
|
||||||
const workspaces = await this.workspaceRepository.find({
|
|
||||||
where: whereCondition,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (options.dryRun) {
|
|
||||||
this.logger.log(chalk.yellow('Dry run mode: No changes will be applied'));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.log(
|
|
||||||
chalk.blue(
|
|
||||||
`Found ${workspaces.length} inactive workspace${
|
|
||||||
workspaces.length > 1 ? 's' : ''
|
|
||||||
}`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
await rawDataSource.initialize();
|
|
||||||
|
|
||||||
for (const workspace of workspaces) {
|
|
||||||
this.logger.log(
|
|
||||||
chalk.blue(
|
|
||||||
`Processing workspace ${workspace.id} with name ${workspace.displayName}`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
// Check if the workspace has a datasource
|
|
||||||
const datasource = await this.datasourceRepository.findOne({
|
|
||||||
where: { workspaceId: workspace.id },
|
|
||||||
});
|
|
||||||
|
|
||||||
const schemaName = datasource?.schema;
|
|
||||||
|
|
||||||
const postgresSchemaExists = await this.typeORMService
|
|
||||||
.getMainDataSource()
|
|
||||||
.query(
|
|
||||||
`SELECT COUNT(*) FROM information_schema.schemata WHERE schema_name = '${schemaName}'`,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!schemaName || !postgresSchemaExists) {
|
|
||||||
await this.deleteWorkspaceAndMarkAsSuspended(workspace, options);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const subscriptions = await this.subscriptionRepository.find({
|
|
||||||
where: { workspaceId: workspace.id },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (subscriptions.length > 1) {
|
|
||||||
this.logger.warn(chalk.red('More than one subscription found'));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const subscription = subscriptions[0];
|
|
||||||
|
|
||||||
if (!subscription) {
|
|
||||||
this.logger.log(chalk.red('No subscription found'));
|
|
||||||
await this.deleteWorkspaceAndMarkAsSuspendedAndDeleteAllData(
|
|
||||||
workspace,
|
|
||||||
schemaName,
|
|
||||||
options,
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const thirtyDaysAgo = new Date();
|
|
||||||
|
|
||||||
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
|
|
||||||
|
|
||||||
if (
|
|
||||||
([
|
|
||||||
SubscriptionStatus.Canceled,
|
|
||||||
SubscriptionStatus.Incomplete,
|
|
||||||
SubscriptionStatus.IncompleteExpired,
|
|
||||||
SubscriptionStatus.Unpaid,
|
|
||||||
SubscriptionStatus.Paused,
|
|
||||||
].includes(subscription.status) &&
|
|
||||||
subscription.canceledAt &&
|
|
||||||
subscription.canceledAt < thirtyDaysAgo) ||
|
|
||||||
(subscription.canceledAt === null &&
|
|
||||||
subscription.updatedAt &&
|
|
||||||
subscription.updatedAt < thirtyDaysAgo)
|
|
||||||
) {
|
|
||||||
await this.deleteWorkspaceAndMarkAsSuspendedAndDeleteAllData(
|
|
||||||
workspace,
|
|
||||||
schemaName,
|
|
||||||
options,
|
|
||||||
subscription,
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.markAsSuspended(workspace, options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async deleteWorkspaceAndMarkAsSuspended(
|
|
||||||
workspace: Workspace,
|
|
||||||
options: UpdateInactiveWorkspaceStatusOptions,
|
|
||||||
) {
|
|
||||||
this.logger.log(
|
|
||||||
chalk.blue('(!!) Deleting workspace and marking as suspended'),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!options.dryRun) {
|
|
||||||
await this.workspaceRepository.update(workspace.id, {
|
|
||||||
activationStatus: WorkspaceActivationStatus.SUSPENDED,
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.workspaceRepository.softRemove({ id: workspace.id });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async deleteWorkspaceAndMarkAsSuspendedAndDeleteAllData(
|
|
||||||
workspace: Workspace,
|
|
||||||
schemaName: string,
|
|
||||||
options: UpdateInactiveWorkspaceStatusOptions,
|
|
||||||
billingSubscription?: BillingSubscription,
|
|
||||||
) {
|
|
||||||
this.logger.warn(
|
|
||||||
chalk.blue(
|
|
||||||
`(!!!) Deleting workspace and marking as suspended and deleting all data for workspace updated at ${workspace.updatedAt} with subscription status ${billingSubscription?.status} and subscription updatedAt ${billingSubscription?.updatedAt} and canceledAt ${billingSubscription?.canceledAt}`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!options.dryRun) {
|
|
||||||
await this.workspaceRepository.update(workspace.id, {
|
|
||||||
activationStatus: WorkspaceActivationStatus.SUSPENDED,
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.workspaceRepository.softRemove({ id: workspace.id });
|
|
||||||
|
|
||||||
await this.datasourceRepository.delete({ workspaceId: workspace.id });
|
|
||||||
await this.workspaceMigrationRepository.delete({
|
|
||||||
workspaceId: workspace.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.featureFlagRepository.delete({ workspaceId: workspace.id });
|
|
||||||
await this.keyValuePairRepository.delete({ workspaceId: workspace.id });
|
|
||||||
|
|
||||||
const userWorkspaces = await this.userWorkspaceRepository.find({
|
|
||||||
where: { workspaceId: workspace.id },
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const userWorkspace of userWorkspaces) {
|
|
||||||
await this.userWorkspaceRepository.delete({ id: userWorkspace.id });
|
|
||||||
|
|
||||||
const remainingUserWorkspaces =
|
|
||||||
await this.userWorkspaceRepository.count({
|
|
||||||
where: { userId: userWorkspace.userId },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (remainingUserWorkspaces === 0) {
|
|
||||||
await this.userRepository.softRemove({ id: userWorkspace.userId });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.typeORMService
|
|
||||||
.getMainDataSource()
|
|
||||||
.query(`DROP SCHEMA IF EXISTS ${schemaName} CASCADE`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async markAsSuspended(
|
|
||||||
workspace: Workspace,
|
|
||||||
options: UpdateInactiveWorkspaceStatusOptions,
|
|
||||||
) {
|
|
||||||
this.logger.log(chalk.blue('(!) Marking as suspended'));
|
|
||||||
if (!options.dryRun) {
|
|
||||||
await this.workspaceRepository.update(workspace.id, {
|
|
||||||
activationStatus: WorkspaceActivationStatus.SUSPENDED,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
import { InjectRepository } from '@nestjs/typeorm';
|
|
||||||
|
|
||||||
import { Command } from 'nest-commander';
|
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
|
|
||||||
import { ActiveWorkspacesCommandRunner } from 'src/database/commands/active-workspaces.command';
|
|
||||||
import { BaseCommandOptions } from 'src/database/commands/base.command';
|
|
||||||
import { MigrateAggregateOperationOptionsCommand } from 'src/database/commands/upgrade-version/0-40/0-40-migrate-aggregate-operations-options.command';
|
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
|
||||||
|
|
||||||
@Command({
|
|
||||||
name: 'upgrade-0.40',
|
|
||||||
description: 'Upgrade to 0.40',
|
|
||||||
})
|
|
||||||
export class UpgradeTo0_40Command extends ActiveWorkspacesCommandRunner {
|
|
||||||
constructor(
|
|
||||||
@InjectRepository(Workspace, 'core')
|
|
||||||
protected readonly workspaceRepository: Repository<Workspace>,
|
|
||||||
private readonly migrateAggregateOperationOptionsCommand: MigrateAggregateOperationOptionsCommand,
|
|
||||||
) {
|
|
||||||
super(workspaceRepository);
|
|
||||||
}
|
|
||||||
|
|
||||||
async executeActiveWorkspacesCommand(
|
|
||||||
passedParam: string[],
|
|
||||||
options: BaseCommandOptions,
|
|
||||||
workspaceIds: string[],
|
|
||||||
): Promise<void> {
|
|
||||||
this.logger.log('Running command to upgrade to 0.40');
|
|
||||||
|
|
||||||
await this.migrateAggregateOperationOptionsCommand.executeActiveWorkspacesCommand(
|
|
||||||
passedParam,
|
|
||||||
options,
|
|
||||||
workspaceIds,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,55 +0,0 @@
|
|||||||
import { Module } from '@nestjs/common';
|
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
||||||
|
|
||||||
import { MigrateAggregateOperationOptionsCommand } from 'src/database/commands/upgrade-version/0-40/0-40-migrate-aggregate-operations-options.command';
|
|
||||||
import { UpdateInactiveWorkspaceStatusCommand } from 'src/database/commands/upgrade-version/0-40/0-40-update-inactive-workspace-status.command';
|
|
||||||
import { UpgradeTo0_40Command } from 'src/database/commands/upgrade-version/0-40/0-40-upgrade-version.command';
|
|
||||||
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
|
|
||||||
import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity';
|
|
||||||
import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
|
||||||
import { KeyValuePair } from 'src/engine/core-modules/key-value-pair/key-value-pair.entity';
|
|
||||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
|
||||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
|
||||||
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.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 { WorkspaceMigrationEntity } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
|
|
||||||
import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module';
|
|
||||||
import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.module';
|
|
||||||
|
|
||||||
@Module({
|
|
||||||
imports: [
|
|
||||||
TypeOrmModule.forFeature(
|
|
||||||
[
|
|
||||||
Workspace,
|
|
||||||
BillingSubscription,
|
|
||||||
FeatureFlag,
|
|
||||||
KeyValuePair,
|
|
||||||
User,
|
|
||||||
UserWorkspace,
|
|
||||||
],
|
|
||||||
'core',
|
|
||||||
),
|
|
||||||
TypeOrmModule.forFeature(
|
|
||||||
[
|
|
||||||
ObjectMetadataEntity,
|
|
||||||
FieldMetadataEntity,
|
|
||||||
DataSourceEntity,
|
|
||||||
WorkspaceMigrationEntity,
|
|
||||||
],
|
|
||||||
'metadata',
|
|
||||||
),
|
|
||||||
WorkspaceMigrationRunnerModule,
|
|
||||||
WorkspaceMigrationModule,
|
|
||||||
WorkspaceMetadataVersionModule,
|
|
||||||
TypeORMModule,
|
|
||||||
],
|
|
||||||
providers: [
|
|
||||||
UpgradeTo0_40Command,
|
|
||||||
MigrateAggregateOperationOptionsCommand,
|
|
||||||
UpdateInactiveWorkspaceStatusCommand,
|
|
||||||
],
|
|
||||||
})
|
|
||||||
export class UpgradeTo0_40CommandModule {}
|
|
||||||
@ -1,195 +0,0 @@
|
|||||||
import { InjectRepository } from '@nestjs/typeorm';
|
|
||||||
|
|
||||||
import chalk from 'chalk';
|
|
||||||
import { Command } from 'nest-commander';
|
|
||||||
import { FieldMetadataType } from 'twenty-shared';
|
|
||||||
import { In, Repository, TableColumn } from 'typeorm';
|
|
||||||
|
|
||||||
import {
|
|
||||||
ActiveWorkspacesCommandOptions,
|
|
||||||
ActiveWorkspacesCommandRunner,
|
|
||||||
} from 'src/database/commands/active-workspaces.command';
|
|
||||||
import { CommandLogger } from 'src/database/commands/logger';
|
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
|
||||||
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
|
||||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
|
||||||
import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service';
|
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
|
||||||
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
|
|
||||||
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
|
|
||||||
|
|
||||||
@Command({
|
|
||||||
name: 'upgrade-0.41:add-context-to-actor-composite-type',
|
|
||||||
description: 'Add context to actor composite type.',
|
|
||||||
})
|
|
||||||
export class AddContextToActorCompositeTypeCommand extends ActiveWorkspacesCommandRunner {
|
|
||||||
protected readonly logger;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
@InjectRepository(Workspace, 'core')
|
|
||||||
protected readonly workspaceRepository: Repository<Workspace>,
|
|
||||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
|
||||||
@InjectRepository(FieldMetadataEntity, 'metadata')
|
|
||||||
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
|
||||||
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
|
|
||||||
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
|
|
||||||
) {
|
|
||||||
super(workspaceRepository);
|
|
||||||
this.logger = new CommandLogger({
|
|
||||||
constructorName: this.constructor.name,
|
|
||||||
verbose: false,
|
|
||||||
});
|
|
||||||
this.logger.setVerbose(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
async executeActiveWorkspacesCommand(
|
|
||||||
_passedParam: string[],
|
|
||||||
options: ActiveWorkspacesCommandOptions,
|
|
||||||
workspaceIds: string[],
|
|
||||||
): Promise<void> {
|
|
||||||
this.logger.log(`Running add-context-to-actor-composite-type command`);
|
|
||||||
|
|
||||||
if (options?.dryRun) {
|
|
||||||
this.logger.log(chalk.yellow('Dry run mode: No changes will be applied'));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const workspaceId of workspaceIds) {
|
|
||||||
try {
|
|
||||||
await this.execute(workspaceId, options?.dryRun);
|
|
||||||
this.logger.verbose(`Added for workspace: ${workspaceId}`);
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(`Error for workspace: ${workspaceId}`, error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async execute(workspaceId: string, dryRun = false): Promise<void> {
|
|
||||||
this.logger.verbose(`Adding for workspace: ${workspaceId}`);
|
|
||||||
const actorFields = await this.fieldMetadataRepository.find({
|
|
||||||
where: {
|
|
||||||
type: FieldMetadataType.ACTOR,
|
|
||||||
workspaceId,
|
|
||||||
},
|
|
||||||
relations: ['object'],
|
|
||||||
});
|
|
||||||
|
|
||||||
// Filter and update fields with EMAIL or CALENDAR source
|
|
||||||
for (const field of actorFields) {
|
|
||||||
if (!field || !field.object) {
|
|
||||||
this.logger.verbose(
|
|
||||||
'field.objectMetadata is null',
|
|
||||||
workspaceId,
|
|
||||||
field.id,
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.addContextColumn(
|
|
||||||
field,
|
|
||||||
`${field.name}Context`,
|
|
||||||
workspaceId,
|
|
||||||
dryRun,
|
|
||||||
);
|
|
||||||
|
|
||||||
const fieldRepository =
|
|
||||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
|
||||||
workspaceId,
|
|
||||||
field.object.nameSingular,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!dryRun) {
|
|
||||||
const rowsToUpdate = await fieldRepository.update(
|
|
||||||
{
|
|
||||||
[field.name + 'Source']: In([
|
|
||||||
FieldActorSource.EMAIL,
|
|
||||||
FieldActorSource.CALENDAR,
|
|
||||||
]),
|
|
||||||
[field.name + 'Context']: {},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[field.name + 'Context']: {
|
|
||||||
provider: 'google',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
this.logger.verbose(
|
|
||||||
`updated ${rowsToUpdate ? rowsToUpdate.affected : 0} rows`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!dryRun) {
|
|
||||||
await this.workspaceMetadataVersionService.incrementMetadataVersion(
|
|
||||||
workspaceId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.twentyORMGlobalManager.destroyDataSourceForWorkspace(
|
|
||||||
workspaceId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async addContextColumn(
|
|
||||||
field: FieldMetadataEntity,
|
|
||||||
newColumnName: string,
|
|
||||||
workspaceId: string,
|
|
||||||
dryRun = false,
|
|
||||||
): Promise<void> {
|
|
||||||
const workspaceDataSource =
|
|
||||||
await this.workspaceDataSourceService.connectToWorkspaceDataSource(
|
|
||||||
workspaceId,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!workspaceDataSource) {
|
|
||||||
this.logger.verbose('No workspace data source found');
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const queryRunner = workspaceDataSource?.createQueryRunner();
|
|
||||||
|
|
||||||
await queryRunner.connect();
|
|
||||||
await queryRunner.startTransaction();
|
|
||||||
|
|
||||||
const schemaName =
|
|
||||||
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const hasColumn = await queryRunner.hasColumn(
|
|
||||||
`${schemaName}.${computeTableName(
|
|
||||||
field.object.nameSingular,
|
|
||||||
field?.object?.isCustom,
|
|
||||||
)}`,
|
|
||||||
newColumnName,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (hasColumn) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!dryRun) {
|
|
||||||
await queryRunner.addColumn(
|
|
||||||
`${schemaName}.${computeTableName(
|
|
||||||
field.object.nameSingular,
|
|
||||||
field?.object?.isCustom,
|
|
||||||
)}`,
|
|
||||||
new TableColumn({
|
|
||||||
name: newColumnName,
|
|
||||||
type: 'jsonb',
|
|
||||||
default: `'{}'::"jsonb"`,
|
|
||||||
isNullable: true,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
await queryRunner.commitTransaction();
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
await queryRunner.rollbackTransaction();
|
|
||||||
|
|
||||||
throw error;
|
|
||||||
} finally {
|
|
||||||
await queryRunner.release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,140 +0,0 @@
|
|||||||
import { InjectRepository } from '@nestjs/typeorm';
|
|
||||||
|
|
||||||
import chalk from 'chalk';
|
|
||||||
import { Command } from 'nest-commander';
|
|
||||||
import { FieldMetadataType } from 'twenty-shared';
|
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
|
|
||||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
|
||||||
|
|
||||||
import {
|
|
||||||
ActiveWorkspacesCommandOptions,
|
|
||||||
ActiveWorkspacesCommandRunner,
|
|
||||||
} from 'src/database/commands/active-workspaces.command';
|
|
||||||
import { isCommandLogger } from 'src/database/commands/logger';
|
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
|
||||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
|
||||||
import {
|
|
||||||
deduceRelationDirection,
|
|
||||||
RelationDirection,
|
|
||||||
} from 'src/engine/utils/deduce-relation-direction.util';
|
|
||||||
|
|
||||||
@Command({
|
|
||||||
name: 'upgrade-0.41:migrate-relations-to-field-metadata',
|
|
||||||
description: 'Migrate relations to field metadata',
|
|
||||||
})
|
|
||||||
export class MigrateRelationsToFieldMetadataCommand extends ActiveWorkspacesCommandRunner {
|
|
||||||
constructor(
|
|
||||||
@InjectRepository(Workspace, 'core')
|
|
||||||
protected readonly workspaceRepository: Repository<Workspace>,
|
|
||||||
@InjectRepository(FieldMetadataEntity, 'metadata')
|
|
||||||
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
|
||||||
) {
|
|
||||||
super(workspaceRepository);
|
|
||||||
}
|
|
||||||
|
|
||||||
async executeActiveWorkspacesCommand(
|
|
||||||
_passedParam: string[],
|
|
||||||
options: ActiveWorkspacesCommandOptions,
|
|
||||||
workspaceIds: string[],
|
|
||||||
): Promise<void> {
|
|
||||||
this.logger.log('Running command to create many to one relations');
|
|
||||||
|
|
||||||
if (isCommandLogger(this.logger)) {
|
|
||||||
this.logger.setVerbose(options.verbose ?? false);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
for (const [index, workspaceId] of workspaceIds.entries()) {
|
|
||||||
await this.processWorkspace(workspaceId, index, workspaceIds.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.log(chalk.green('Command completed!'));
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.log(chalk.red('Error in workspace'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async processWorkspace(
|
|
||||||
workspaceId: string,
|
|
||||||
index: number,
|
|
||||||
total: number,
|
|
||||||
): Promise<void> {
|
|
||||||
try {
|
|
||||||
this.logger.log(
|
|
||||||
`Running command for workspace ${workspaceId} ${index + 1}/${total}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
const fieldMetadataCollection = (await this.fieldMetadataRepository.find({
|
|
||||||
where: { workspaceId, type: FieldMetadataType.RELATION },
|
|
||||||
relations: ['fromRelationMetadata', 'toRelationMetadata'],
|
|
||||||
})) as unknown as FieldMetadataEntity<FieldMetadataType.RELATION>[];
|
|
||||||
|
|
||||||
if (!fieldMetadataCollection.length) {
|
|
||||||
this.logger.log(
|
|
||||||
chalk.yellow(
|
|
||||||
`No relation field metadata found for workspace ${workspaceId}.`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fieldMetadataToUpdateCollection = fieldMetadataCollection.map(
|
|
||||||
(fieldMetadata) => this.mapFieldMetadata(fieldMetadata),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (fieldMetadataToUpdateCollection.length > 0) {
|
|
||||||
await this.fieldMetadataRepository.save(
|
|
||||||
fieldMetadataToUpdateCollection,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.log(
|
|
||||||
chalk.green(`Command completed for workspace ${workspaceId}.`),
|
|
||||||
);
|
|
||||||
} catch {
|
|
||||||
this.logger.log(chalk.red(`Error in workspace ${workspaceId}.`));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private mapFieldMetadata(
|
|
||||||
fieldMetadata: FieldMetadataEntity<FieldMetadataType.RELATION>,
|
|
||||||
): FieldMetadataEntity<FieldMetadataType.RELATION> {
|
|
||||||
const relationMetadata =
|
|
||||||
fieldMetadata.fromRelationMetadata ?? fieldMetadata.toRelationMetadata;
|
|
||||||
|
|
||||||
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: relationMetadata.onDeleteAction,
|
|
||||||
},
|
|
||||||
relationTargetFieldMetadataId,
|
|
||||||
relationTargetObjectMetadataId,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,98 +0,0 @@
|
|||||||
import { Logger } from '@nestjs/common';
|
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
|
||||||
|
|
||||||
import { Command } from 'nest-commander';
|
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
|
|
||||||
import {
|
|
||||||
ActiveWorkspacesCommandOptions,
|
|
||||||
ActiveWorkspacesCommandRunner,
|
|
||||||
} from 'src/database/commands/active-workspaces.command';
|
|
||||||
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.41:remove-duplicate-mcmas',
|
|
||||||
description: 'Remove duplicate mcmas.',
|
|
||||||
})
|
|
||||||
export class RemoveDuplicateMcmasCommand extends ActiveWorkspacesCommandRunner {
|
|
||||||
protected readonly logger: Logger;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
@InjectRepository(Workspace, 'core')
|
|
||||||
protected readonly workspaceRepository: Repository<Workspace>,
|
|
||||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
|
||||||
) {
|
|
||||||
super(workspaceRepository);
|
|
||||||
this.logger = new Logger(this.constructor.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
async executeActiveWorkspacesCommand(
|
|
||||||
_passedParam: string[],
|
|
||||||
_options: ActiveWorkspacesCommandOptions,
|
|
||||||
workspaceIds: string[],
|
|
||||||
): Promise<void> {
|
|
||||||
const { dryRun } = _options;
|
|
||||||
|
|
||||||
for (const workspaceId of workspaceIds) {
|
|
||||||
try {
|
|
||||||
await this.execute(workspaceId, dryRun);
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(
|
|
||||||
`Error removing duplicate mcmas for workspace ${workspaceId}: ${error}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async execute(workspaceId: string, dryRun = false): Promise<void> {
|
|
||||||
this.logger.log(`Removing duplicate mcmas for workspace: ${workspaceId}`);
|
|
||||||
|
|
||||||
const repository =
|
|
||||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
|
||||||
workspaceId,
|
|
||||||
'messageChannelMessageAssociation',
|
|
||||||
);
|
|
||||||
|
|
||||||
const queryBuilder = repository.createQueryBuilder(
|
|
||||||
'messageChannelMessageAssociation',
|
|
||||||
);
|
|
||||||
|
|
||||||
const duplicateMcmas = await queryBuilder
|
|
||||||
.select(`"messageChannelId"`)
|
|
||||||
.addSelect(`"messageId"`)
|
|
||||||
.where(`"deletedAt" IS NULL`)
|
|
||||||
.groupBy(`"messageId"`)
|
|
||||||
.addGroupBy(`"messageChannelId"`)
|
|
||||||
.having(`COUNT("messageChannelId") > 1`)
|
|
||||||
.getRawMany();
|
|
||||||
|
|
||||||
this.logger.log(`Found ${duplicateMcmas.length} duplicate mcmas`);
|
|
||||||
|
|
||||||
for (const duplicateMca of duplicateMcmas) {
|
|
||||||
const mcmas = await repository.find({
|
|
||||||
where: {
|
|
||||||
messageId: duplicateMca.messageId,
|
|
||||||
messageChannelId: duplicateMca.messageChannelId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
this.logger.log(
|
|
||||||
`Found ${mcmas.length} mcmas for message ${duplicateMca.messageId} and message channel ${duplicateMca.messageChannelId}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
const mcaIdsToDelete = mcmas.slice(1).map((mca) => mca.id);
|
|
||||||
|
|
||||||
if (mcaIdsToDelete.length > 0) {
|
|
||||||
this.logger.log(`Deleting ${mcaIdsToDelete.length} mcas`);
|
|
||||||
if (!dryRun) {
|
|
||||||
await repository.delete(mcaIdsToDelete);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.twentyORMGlobalManager.destroyDataSourceForWorkspace(
|
|
||||||
workspaceId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,207 +0,0 @@
|
|||||||
import { Logger } from '@nestjs/common';
|
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
|
||||||
|
|
||||||
import { Command } from 'nest-commander';
|
|
||||||
import { EntityManager, IsNull, Not, Repository } from 'typeorm';
|
|
||||||
|
|
||||||
import {
|
|
||||||
ActiveWorkspacesCommandOptions,
|
|
||||||
ActiveWorkspacesCommandRunner,
|
|
||||||
} from 'src/database/commands/active-workspaces.command';
|
|
||||||
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
|
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
|
||||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
|
||||||
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
|
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
|
||||||
import { createWorkspaceViews } from 'src/engine/workspace-manager/standard-objects-prefill-data/create-workspace-views';
|
|
||||||
import { workflowRunsAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/workflow-runs-all.view';
|
|
||||||
import { workflowVersionsAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/workflow-versions-all.view';
|
|
||||||
import { workflowsAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/workflows-all.view';
|
|
||||||
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
|
|
||||||
|
|
||||||
@Command({
|
|
||||||
name: 'upgrade-0.41:workflow-seed-views',
|
|
||||||
description: 'Seed workflow views for workspace.',
|
|
||||||
})
|
|
||||||
export class SeedWorkflowViewsCommand extends ActiveWorkspacesCommandRunner {
|
|
||||||
protected readonly logger: Logger;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
@InjectRepository(Workspace, 'core')
|
|
||||||
protected readonly workspaceRepository: Repository<Workspace>,
|
|
||||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
|
||||||
private readonly dataSourceService: DataSourceService,
|
|
||||||
private readonly typeORMService: TypeORMService,
|
|
||||||
private readonly objectMetadataService: ObjectMetadataService,
|
|
||||||
) {
|
|
||||||
super(workspaceRepository);
|
|
||||||
this.logger = new Logger(this.constructor.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
async executeActiveWorkspacesCommand(
|
|
||||||
_passedParam: string[],
|
|
||||||
_options: ActiveWorkspacesCommandOptions,
|
|
||||||
_workspaceIds: string[],
|
|
||||||
): Promise<void> {
|
|
||||||
const { dryRun } = _options;
|
|
||||||
|
|
||||||
for (const workspaceId of _workspaceIds) {
|
|
||||||
await this.execute(workspaceId, dryRun);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async execute(workspaceId: string, dryRun = false): Promise<void> {
|
|
||||||
this.logger.log(`Seeding workflow views for workspace: ${workspaceId}`);
|
|
||||||
|
|
||||||
const workflowObjectMetadata =
|
|
||||||
await this.objectMetadataService.findOneWithinWorkspace(workspaceId, {
|
|
||||||
where: {
|
|
||||||
standardId: STANDARD_OBJECT_IDS.workflow,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!workflowObjectMetadata) {
|
|
||||||
this.logger.error('Workflow object metadata not found');
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.seedWorkflowViews(
|
|
||||||
workspaceId,
|
|
||||||
workflowObjectMetadata.id,
|
|
||||||
dryRun,
|
|
||||||
);
|
|
||||||
|
|
||||||
await this.seedWorkspaceFavorite(
|
|
||||||
workspaceId,
|
|
||||||
workflowObjectMetadata.id,
|
|
||||||
dryRun,
|
|
||||||
);
|
|
||||||
|
|
||||||
await this.twentyORMGlobalManager.destroyDataSourceForWorkspace(
|
|
||||||
workspaceId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async seedWorkflowViews(
|
|
||||||
workspaceId: string,
|
|
||||||
workflowObjectMetadataId: string,
|
|
||||||
dryRun = false,
|
|
||||||
) {
|
|
||||||
const viewRepository =
|
|
||||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
|
||||||
workspaceId,
|
|
||||||
'view',
|
|
||||||
);
|
|
||||||
|
|
||||||
const existingWorkflowView = await viewRepository.findOne({
|
|
||||||
where: {
|
|
||||||
objectMetadataId: workflowObjectMetadataId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (existingWorkflowView) {
|
|
||||||
this.logger.log(`View already exists: ${existingWorkflowView.id}`);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dryRun) {
|
|
||||||
this.logger.log(`Dry run: not creating view`);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { objectMetadataStandardIdToIdMap } =
|
|
||||||
await this.objectMetadataService.getObjectMetadataStandardIdToIdMap(
|
|
||||||
workspaceId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const dataSourceMetadata =
|
|
||||||
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
|
|
||||||
workspaceId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const workspaceDataSource =
|
|
||||||
await this.typeORMService.connectToDataSource(dataSourceMetadata);
|
|
||||||
|
|
||||||
if (!workspaceDataSource) {
|
|
||||||
this.logger.error('Could not connect to workspace data source');
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const viewDefinitions = [
|
|
||||||
workflowsAllView(objectMetadataStandardIdToIdMap),
|
|
||||||
workflowVersionsAllView(objectMetadataStandardIdToIdMap),
|
|
||||||
workflowRunsAllView(objectMetadataStandardIdToIdMap),
|
|
||||||
];
|
|
||||||
|
|
||||||
await workspaceDataSource.transaction(
|
|
||||||
async (entityManager: EntityManager) => {
|
|
||||||
return createWorkspaceViews(
|
|
||||||
entityManager,
|
|
||||||
dataSourceMetadata.schema,
|
|
||||||
viewDefinitions,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async seedWorkspaceFavorite(
|
|
||||||
workspaceId: string,
|
|
||||||
workflowObjectMetadataId: string,
|
|
||||||
dryRun = false,
|
|
||||||
) {
|
|
||||||
const viewRepository =
|
|
||||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
|
||||||
workspaceId,
|
|
||||||
'view',
|
|
||||||
);
|
|
||||||
|
|
||||||
const workflowView = await viewRepository.findOne({
|
|
||||||
where: {
|
|
||||||
objectMetadataId: workflowObjectMetadataId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!workflowView) {
|
|
||||||
this.logger.error('Workflow view not found');
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dryRun) {
|
|
||||||
this.logger.log(`Dry run: not creating favorite`);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const favoriteRepository =
|
|
||||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
|
||||||
workspaceId,
|
|
||||||
'favorite',
|
|
||||||
);
|
|
||||||
|
|
||||||
const existingFavorites = await favoriteRepository.find({
|
|
||||||
where: {
|
|
||||||
viewId: Not(IsNull()),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const workflowFavorite = existingFavorites.find(
|
|
||||||
(favorite) => favorite.viewId === workflowView.id,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (workflowFavorite) {
|
|
||||||
this.logger.log(`Favorite already exists: ${workflowFavorite.id}`);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await favoriteRepository.insert({
|
|
||||||
viewId: workflowView.id,
|
|
||||||
position: existingFavorites.length,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,72 +0,0 @@
|
|||||||
import { InjectRepository } from '@nestjs/typeorm';
|
|
||||||
|
|
||||||
import { Command } from 'nest-commander';
|
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
|
|
||||||
import { ActiveWorkspacesCommandRunner } from 'src/database/commands/active-workspaces.command';
|
|
||||||
import { BaseCommandOptions } from 'src/database/commands/base.command';
|
|
||||||
import { AddContextToActorCompositeTypeCommand } from 'src/database/commands/upgrade-version/0-41/0-41-add-context-to-actor-composite-type';
|
|
||||||
import { MigrateRelationsToFieldMetadataCommand } from 'src/database/commands/upgrade-version/0-41/0-41-migrate-relations-to-field-metadata.command';
|
|
||||||
import { RemoveDuplicateMcmasCommand } from 'src/database/commands/upgrade-version/0-41/0-41-remove-duplicate-mcmas';
|
|
||||||
import { SeedWorkflowViewsCommand } from 'src/database/commands/upgrade-version/0-41/0-41-seed-workflow-views.command';
|
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
|
||||||
import { SyncWorkspaceMetadataCommand } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/sync-workspace-metadata.command';
|
|
||||||
|
|
||||||
@Command({
|
|
||||||
name: 'upgrade-0.41',
|
|
||||||
description: 'Upgrade to 0.41',
|
|
||||||
})
|
|
||||||
export class UpgradeTo0_41Command extends ActiveWorkspacesCommandRunner {
|
|
||||||
constructor(
|
|
||||||
@InjectRepository(Workspace, 'core')
|
|
||||||
protected readonly workspaceRepository: Repository<Workspace>,
|
|
||||||
private readonly seedWorkflowViewsCommand: SeedWorkflowViewsCommand,
|
|
||||||
private readonly syncWorkspaceMetadataCommand: SyncWorkspaceMetadataCommand,
|
|
||||||
private readonly migrateRelationsToFieldMetadata: MigrateRelationsToFieldMetadataCommand,
|
|
||||||
private readonly addContextToActorCompositeType: AddContextToActorCompositeTypeCommand,
|
|
||||||
private readonly removeDuplicateMcmasCommand: RemoveDuplicateMcmasCommand,
|
|
||||||
) {
|
|
||||||
super(workspaceRepository);
|
|
||||||
}
|
|
||||||
|
|
||||||
async executeActiveWorkspacesCommand(
|
|
||||||
passedParam: string[],
|
|
||||||
options: BaseCommandOptions,
|
|
||||||
workspaceIds: string[],
|
|
||||||
): Promise<void> {
|
|
||||||
this.logger.log('Running command to upgrade to 0.41');
|
|
||||||
|
|
||||||
await this.removeDuplicateMcmasCommand.executeActiveWorkspacesCommand(
|
|
||||||
passedParam,
|
|
||||||
options,
|
|
||||||
workspaceIds,
|
|
||||||
);
|
|
||||||
|
|
||||||
await this.addContextToActorCompositeType.executeActiveWorkspacesCommand(
|
|
||||||
passedParam,
|
|
||||||
options,
|
|
||||||
workspaceIds,
|
|
||||||
);
|
|
||||||
|
|
||||||
await this.syncWorkspaceMetadataCommand.executeActiveWorkspacesCommand(
|
|
||||||
passedParam,
|
|
||||||
{
|
|
||||||
...options,
|
|
||||||
force: true,
|
|
||||||
},
|
|
||||||
workspaceIds,
|
|
||||||
);
|
|
||||||
|
|
||||||
await this.seedWorkflowViewsCommand.executeActiveWorkspacesCommand(
|
|
||||||
passedParam,
|
|
||||||
options,
|
|
||||||
workspaceIds,
|
|
||||||
);
|
|
||||||
|
|
||||||
await this.migrateRelationsToFieldMetadata.executeActiveWorkspacesCommand(
|
|
||||||
passedParam,
|
|
||||||
options,
|
|
||||||
workspaceIds,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
import { Module } from '@nestjs/common';
|
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
||||||
|
|
||||||
import { AddContextToActorCompositeTypeCommand } from 'src/database/commands/upgrade-version/0-41/0-41-add-context-to-actor-composite-type';
|
|
||||||
import { MigrateRelationsToFieldMetadataCommand } from 'src/database/commands/upgrade-version/0-41/0-41-migrate-relations-to-field-metadata.command';
|
|
||||||
import { RemoveDuplicateMcmasCommand } from 'src/database/commands/upgrade-version/0-41/0-41-remove-duplicate-mcmas';
|
|
||||||
import { SeedWorkflowViewsCommand } from 'src/database/commands/upgrade-version/0-41/0-41-seed-workflow-views.command';
|
|
||||||
import { UpgradeTo0_41Command } from 'src/database/commands/upgrade-version/0-41/0-41-upgrade-version.command';
|
|
||||||
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
|
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
|
||||||
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
|
||||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
|
||||||
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.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 { WorkspaceHealthModule } from 'src/engine/workspace-manager/workspace-health/workspace-health.module';
|
|
||||||
import { SyncWorkspaceLoggerService } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/services/sync-workspace-logger.service';
|
|
||||||
import { SyncWorkspaceMetadataCommand } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/sync-workspace-metadata.command';
|
|
||||||
import { WorkspaceSyncMetadataCommandsModule } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/workspace-sync-metadata-commands.module';
|
|
||||||
import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.module';
|
|
||||||
|
|
||||||
@Module({
|
|
||||||
imports: [
|
|
||||||
TypeOrmModule.forFeature([Workspace], 'core'),
|
|
||||||
TypeOrmModule.forFeature([FieldMetadataEntity], 'metadata'),
|
|
||||||
TypeORMModule,
|
|
||||||
DataSourceModule,
|
|
||||||
ObjectMetadataModule,
|
|
||||||
WorkspaceSyncMetadataCommandsModule,
|
|
||||||
WorkspaceSyncMetadataModule,
|
|
||||||
WorkspaceHealthModule,
|
|
||||||
WorkspaceDataSourceModule,
|
|
||||||
WorkspaceMetadataVersionModule,
|
|
||||||
],
|
|
||||||
providers: [
|
|
||||||
SyncWorkspaceLoggerService,
|
|
||||||
SyncWorkspaceMetadataCommand,
|
|
||||||
SeedWorkflowViewsCommand,
|
|
||||||
UpgradeTo0_41Command,
|
|
||||||
MigrateRelationsToFieldMetadataCommand,
|
|
||||||
AddContextToActorCompositeTypeCommand,
|
|
||||||
RemoveDuplicateMcmasCommand,
|
|
||||||
],
|
|
||||||
})
|
|
||||||
export class UpgradeTo0_41CommandModule {}
|
|
||||||
@ -26,10 +26,10 @@ export class FixBodyV2ViewFieldPositionCommand extends ActiveWorkspacesCommandRu
|
|||||||
protected readonly workspaceRepository: Repository<Workspace>,
|
protected readonly workspaceRepository: Repository<Workspace>,
|
||||||
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
||||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
|
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
|
||||||
) {
|
) {
|
||||||
super(workspaceRepository);
|
super(workspaceRepository, twentyORMGlobalManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
async executeActiveWorkspacesCommand(
|
async executeActiveWorkspacesCommand(
|
||||||
|
|||||||
@ -25,9 +25,9 @@ export class LimitAmountOfViewFieldCommand extends ActiveWorkspacesCommandRunner
|
|||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(Workspace, 'core')
|
@InjectRepository(Workspace, 'core')
|
||||||
protected readonly workspaceRepository: Repository<Workspace>,
|
protected readonly workspaceRepository: Repository<Workspace>,
|
||||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
) {
|
) {
|
||||||
super(workspaceRepository);
|
super(workspaceRepository, twentyORMGlobalManager);
|
||||||
this.logger = new CommandLogger({
|
this.logger = new CommandLogger({
|
||||||
constructorName: this.constructor.name,
|
constructorName: this.constructor.name,
|
||||||
verbose: false,
|
verbose: false,
|
||||||
|
|||||||
@ -64,6 +64,7 @@ export class MigrateRichTextFieldCommand extends ActiveWorkspacesCommandRunner {
|
|||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(Workspace, 'core')
|
@InjectRepository(Workspace, 'core')
|
||||||
protected readonly workspaceRepository: Repository<Workspace>,
|
protected readonly workspaceRepository: Repository<Workspace>,
|
||||||
|
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
@InjectRepository(FieldMetadataEntity, 'metadata')
|
@InjectRepository(FieldMetadataEntity, 'metadata')
|
||||||
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
||||||
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
||||||
@ -71,12 +72,11 @@ export class MigrateRichTextFieldCommand extends ActiveWorkspacesCommandRunner {
|
|||||||
@InjectRepository(FeatureFlag, 'core')
|
@InjectRepository(FeatureFlag, 'core')
|
||||||
protected readonly featureFlagRepository: Repository<FeatureFlag>,
|
protected readonly featureFlagRepository: Repository<FeatureFlag>,
|
||||||
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
|
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
|
||||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
|
||||||
private readonly workspaceMigrationService: WorkspaceMigrationService,
|
private readonly workspaceMigrationService: WorkspaceMigrationService,
|
||||||
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
|
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
|
||||||
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
|
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
|
||||||
) {
|
) {
|
||||||
super(workspaceRepository);
|
super(workspaceRepository, twentyORMGlobalManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Option({
|
@Option({
|
||||||
|
|||||||
@ -25,12 +25,13 @@ export class StandardizationOfActorCompositeContextTypeCommand extends ActiveWor
|
|||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(Workspace, 'core')
|
@InjectRepository(Workspace, 'core')
|
||||||
protected readonly workspaceRepository: Repository<Workspace>,
|
protected readonly workspaceRepository: Repository<Workspace>,
|
||||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
@InjectRepository(FieldMetadataEntity, 'metadata')
|
@InjectRepository(FieldMetadataEntity, 'metadata')
|
||||||
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
||||||
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
|
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
|
||||||
) {
|
) {
|
||||||
super(workspaceRepository);
|
super(workspaceRepository, twentyORMGlobalManager);
|
||||||
|
|
||||||
this.logger = new CommandLogger({
|
this.logger = new CommandLogger({
|
||||||
constructorName: this.constructor.name,
|
constructorName: this.constructor.name,
|
||||||
verbose: false,
|
verbose: false,
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { LimitAmountOfViewFieldCommand } from 'src/database/commands/upgrade-ver
|
|||||||
import { MigrateRichTextFieldCommand } from 'src/database/commands/upgrade-version/0-42/0-42-migrate-rich-text-field.command';
|
import { MigrateRichTextFieldCommand } from 'src/database/commands/upgrade-version/0-42/0-42-migrate-rich-text-field.command';
|
||||||
import { StandardizationOfActorCompositeContextTypeCommand } from 'src/database/commands/upgrade-version/0-42/0-42-standardization-of-actor-composite-context-type';
|
import { StandardizationOfActorCompositeContextTypeCommand } from 'src/database/commands/upgrade-version/0-42/0-42-standardization-of-actor-composite-context-type';
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
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 { SyncWorkspaceMetadataCommand } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/sync-workspace-metadata.command';
|
||||||
|
|
||||||
type Upgrade042CommandCustomOptions = {
|
type Upgrade042CommandCustomOptions = {
|
||||||
@ -25,13 +26,14 @@ export class UpgradeTo0_42Command extends ActiveWorkspacesCommandRunner {
|
|||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(Workspace, 'core')
|
@InjectRepository(Workspace, 'core')
|
||||||
protected readonly workspaceRepository: Repository<Workspace>,
|
protected readonly workspaceRepository: Repository<Workspace>,
|
||||||
|
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
private readonly migrateRichTextFieldCommand: MigrateRichTextFieldCommand,
|
private readonly migrateRichTextFieldCommand: MigrateRichTextFieldCommand,
|
||||||
private readonly fixBodyV2ViewFieldPositionCommand: FixBodyV2ViewFieldPositionCommand,
|
private readonly fixBodyV2ViewFieldPositionCommand: FixBodyV2ViewFieldPositionCommand,
|
||||||
private readonly limitAmountOfViewFieldCommand: LimitAmountOfViewFieldCommand,
|
private readonly limitAmountOfViewFieldCommand: LimitAmountOfViewFieldCommand,
|
||||||
private readonly syncWorkspaceMetadataCommand: SyncWorkspaceMetadataCommand,
|
private readonly syncWorkspaceMetadataCommand: SyncWorkspaceMetadataCommand,
|
||||||
private readonly standardizationOfActorCompositeContextType: StandardizationOfActorCompositeContextTypeCommand,
|
private readonly standardizationOfActorCompositeContextType: StandardizationOfActorCompositeContextTypeCommand,
|
||||||
) {
|
) {
|
||||||
super(workspaceRepository);
|
super(workspaceRepository, twentyORMGlobalManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Option({
|
@Option({
|
||||||
|
|||||||
@ -17,7 +17,6 @@ import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadat
|
|||||||
import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.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 { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import { tasksAssignedToMeView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/tasks-assigned-to-me';
|
import { tasksAssignedToMeView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/tasks-assigned-to-me';
|
||||||
import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service';
|
|
||||||
import { TASK_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
|
import { TASK_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
|
||||||
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
|
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
|
||||||
import { ViewFieldWorkspaceEntity } from 'src/modules/view/standard-objects/view-field.workspace-entity';
|
import { ViewFieldWorkspaceEntity } from 'src/modules/view/standard-objects/view-field.workspace-entity';
|
||||||
@ -37,11 +36,10 @@ export class AddTasksAssignedToMeViewCommand extends ActiveWorkspacesCommandRunn
|
|||||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||||
@InjectRepository(FieldMetadataEntity, 'metadata')
|
@InjectRepository(FieldMetadataEntity, 'metadata')
|
||||||
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
||||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
|
|
||||||
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
|
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
|
||||||
) {
|
) {
|
||||||
super(workspaceRepository);
|
super(workspaceRepository, twentyORMGlobalManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
async executeActiveWorkspacesCommand(
|
async executeActiveWorkspacesCommand(
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
|||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
import { SearchService } from 'src/engine/metadata-modules/search/search.service';
|
import { SearchService } from 'src/engine/metadata-modules/search/search.service';
|
||||||
import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.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 { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service';
|
||||||
import { SEARCH_FIELDS_FOR_NOTES } from 'src/modules/note/standard-objects/note.workspace-entity';
|
import { SEARCH_FIELDS_FOR_NOTES } from 'src/modules/note/standard-objects/note.workspace-entity';
|
||||||
import { SEARCH_FIELDS_FOR_TASKS } from 'src/modules/task/standard-objects/task.workspace-entity';
|
import { SEARCH_FIELDS_FOR_TASKS } from 'src/modules/task/standard-objects/task.workspace-entity';
|
||||||
@ -26,6 +27,7 @@ export class MigrateSearchVectorOnNoteAndTaskEntitiesCommand extends ActiveWorks
|
|||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(Workspace, 'core')
|
@InjectRepository(Workspace, 'core')
|
||||||
protected readonly workspaceRepository: Repository<Workspace>,
|
protected readonly workspaceRepository: Repository<Workspace>,
|
||||||
|
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
@InjectRepository(FeatureFlag, 'core')
|
@InjectRepository(FeatureFlag, 'core')
|
||||||
protected readonly featureFlagRepository: Repository<FeatureFlag>,
|
protected readonly featureFlagRepository: Repository<FeatureFlag>,
|
||||||
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
||||||
@ -34,7 +36,7 @@ export class MigrateSearchVectorOnNoteAndTaskEntitiesCommand extends ActiveWorks
|
|||||||
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
|
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
|
||||||
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
|
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
|
||||||
) {
|
) {
|
||||||
super(workspaceRepository);
|
super(workspaceRepository, twentyORMGlobalManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
async executeActiveWorkspacesCommand(
|
async executeActiveWorkspacesCommand(
|
||||||
|
|||||||
@ -29,9 +29,9 @@ export class UpdateDefaultViewRecordOpeningOnWorkflowObjectsCommand extends Acti
|
|||||||
protected readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
protected readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||||
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
|
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
|
||||||
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
|
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
|
||||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
) {
|
) {
|
||||||
super(workspaceRepository);
|
super(workspaceRepository, twentyORMGlobalManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
async executeActiveWorkspacesCommand(
|
async executeActiveWorkspacesCommand(
|
||||||
@ -43,9 +43,12 @@ export class UpdateDefaultViewRecordOpeningOnWorkflowObjectsCommand extends Acti
|
|||||||
'Running command to update default view record opening on workflow objects to record page',
|
'Running command to update default view record opening on workflow objects to record page',
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const [index, workspaceId] of workspaceIds.entries()) {
|
this.processEachWorkspaceWithWorkspaceDataSource(
|
||||||
await this.processWorkspace(workspaceId, index, workspaceIds.length);
|
workspaceIds,
|
||||||
}
|
async ({ workspaceId, index, total }) => {
|
||||||
|
await this.processWorkspace(workspaceId, index, total);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
this.logger.log(chalk.green('Command completed!'));
|
this.logger.log(chalk.green('Command completed!'));
|
||||||
}
|
}
|
||||||
@ -87,14 +90,6 @@ export class UpdateDefaultViewRecordOpeningOnWorkflowObjectsCommand extends Acti
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
);
|
);
|
||||||
|
|
||||||
await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations(
|
|
||||||
workspaceId,
|
|
||||||
);
|
|
||||||
|
|
||||||
await this.workspaceMetadataVersionService.incrementMetadataVersion(
|
|
||||||
workspaceId,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
chalk.green(`Command completed for workspace ${workspaceId}.`),
|
chalk.green(`Command completed for workspace ${workspaceId}.`),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { AddTasksAssignedToMeViewCommand } from 'src/database/commands/upgrade-v
|
|||||||
import { MigrateSearchVectorOnNoteAndTaskEntitiesCommand } from 'src/database/commands/upgrade-version/0-43/0-43-migrate-search-vector-on-note-and-task-entities.command';
|
import { MigrateSearchVectorOnNoteAndTaskEntitiesCommand } from 'src/database/commands/upgrade-version/0-43/0-43-migrate-search-vector-on-note-and-task-entities.command';
|
||||||
import { UpdateDefaultViewRecordOpeningOnWorkflowObjectsCommand } from 'src/database/commands/upgrade-version/0-43/0-43-update-default-view-record-opening-on-workflow-objects.command';
|
import { UpdateDefaultViewRecordOpeningOnWorkflowObjectsCommand } from 'src/database/commands/upgrade-version/0-43/0-43-update-default-view-record-opening-on-workflow-objects.command';
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
|
|
||||||
@Command({
|
@Command({
|
||||||
name: 'upgrade-0.43',
|
name: 'upgrade-0.43',
|
||||||
@ -19,12 +20,13 @@ export class UpgradeTo0_43Command extends ActiveWorkspacesCommandRunner {
|
|||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(Workspace, 'core')
|
@InjectRepository(Workspace, 'core')
|
||||||
protected readonly workspaceRepository: Repository<Workspace>,
|
protected readonly workspaceRepository: Repository<Workspace>,
|
||||||
|
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
private readonly addTasksAssignedToMeViewCommand: AddTasksAssignedToMeViewCommand,
|
private readonly addTasksAssignedToMeViewCommand: AddTasksAssignedToMeViewCommand,
|
||||||
private readonly migrateSearchVectorOnNoteAndTaskEntitiesCommand: MigrateSearchVectorOnNoteAndTaskEntitiesCommand,
|
private readonly migrateSearchVectorOnNoteAndTaskEntitiesCommand: MigrateSearchVectorOnNoteAndTaskEntitiesCommand,
|
||||||
private readonly updateDefaultViewRecordOpeningOnWorkflowObjectsCommand: UpdateDefaultViewRecordOpeningOnWorkflowObjectsCommand,
|
private readonly updateDefaultViewRecordOpeningOnWorkflowObjectsCommand: UpdateDefaultViewRecordOpeningOnWorkflowObjectsCommand,
|
||||||
private readonly standardizationOfActorCompositeContextTypeCommand: StandardizationOfActorCompositeContextTypeCommand,
|
private readonly standardizationOfActorCompositeContextTypeCommand: StandardizationOfActorCompositeContextTypeCommand,
|
||||||
) {
|
) {
|
||||||
super(workspaceRepository);
|
super(workspaceRepository, twentyORMGlobalManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
async executeActiveWorkspacesCommand(
|
async executeActiveWorkspacesCommand(
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import {
|
|||||||
import { BillingCustomer } from 'src/engine/core-modules/billing/entities/billing-customer.entity';
|
import { BillingCustomer } from 'src/engine/core-modules/billing/entities/billing-customer.entity';
|
||||||
import { StripeSubscriptionService } from 'src/engine/core-modules/billing/stripe/services/stripe-subscription.service';
|
import { StripeSubscriptionService } from 'src/engine/core-modules/billing/stripe/services/stripe-subscription.service';
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
|
|
||||||
interface SyncCustomerDataCommandOptions
|
interface SyncCustomerDataCommandOptions
|
||||||
extends ActiveWorkspacesCommandOptions {}
|
extends ActiveWorkspacesCommandOptions {}
|
||||||
@ -28,8 +29,9 @@ export class BillingSyncCustomerDataCommand extends ActiveWorkspacesCommandRunne
|
|||||||
private readonly stripeSubscriptionService: StripeSubscriptionService,
|
private readonly stripeSubscriptionService: StripeSubscriptionService,
|
||||||
@InjectRepository(BillingCustomer, 'core')
|
@InjectRepository(BillingCustomer, 'core')
|
||||||
protected readonly billingCustomerRepository: Repository<BillingCustomer>,
|
protected readonly billingCustomerRepository: Repository<BillingCustomer>,
|
||||||
|
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
) {
|
) {
|
||||||
super(workspaceRepository);
|
super(workspaceRepository, twentyORMGlobalManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
async executeActiveWorkspacesCommand(
|
async executeActiveWorkspacesCommand(
|
||||||
|
|||||||
@ -14,13 +14,17 @@ import {
|
|||||||
} from 'src/engine/twenty-orm/exceptions/twenty-orm.exception';
|
} from 'src/engine/twenty-orm/exceptions/twenty-orm.exception';
|
||||||
import { EntitySchemaFactory } from 'src/engine/twenty-orm/factories/entity-schema.factory';
|
import { EntitySchemaFactory } from 'src/engine/twenty-orm/factories/entity-schema.factory';
|
||||||
import { CacheManager } from 'src/engine/twenty-orm/storage/cache-manager.storage';
|
import { CacheManager } from 'src/engine/twenty-orm/storage/cache-manager.storage';
|
||||||
|
import { CacheKey } from 'src/engine/twenty-orm/storage/types/cache-key.type';
|
||||||
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class WorkspaceDatasourceFactory {
|
export class WorkspaceDatasourceFactory {
|
||||||
private readonly logger = new Logger(WorkspaceDatasourceFactory.name);
|
private readonly logger = new Logger(WorkspaceDatasourceFactory.name);
|
||||||
private cacheManager = new CacheManager<WorkspaceDataSource>();
|
private cacheManager = new CacheManager<WorkspaceDataSource>();
|
||||||
private cachedDatasourcePromise: Record<string, Promise<WorkspaceDataSource>>;
|
private cachedDataSourcePromise: Record<
|
||||||
|
CacheKey,
|
||||||
|
Promise<WorkspaceDataSource>
|
||||||
|
>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly dataSourceService: DataSourceService,
|
private readonly dataSourceService: DataSourceService,
|
||||||
@ -29,7 +33,7 @@ export class WorkspaceDatasourceFactory {
|
|||||||
private readonly workspaceMetadataCacheService: WorkspaceMetadataCacheService,
|
private readonly workspaceMetadataCacheService: WorkspaceMetadataCacheService,
|
||||||
private readonly entitySchemaFactory: EntitySchemaFactory,
|
private readonly entitySchemaFactory: EntitySchemaFactory,
|
||||||
) {
|
) {
|
||||||
this.cachedDatasourcePromise = {};
|
this.cachedDataSourcePromise = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
public async create(
|
public async create(
|
||||||
@ -53,16 +57,16 @@ export class WorkspaceDatasourceFactory {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const cacheKey = `${workspaceId}-${cachedWorkspaceMetadataVersion}`;
|
const cacheKey: CacheKey = `${workspaceId}-${cachedWorkspaceMetadataVersion}`;
|
||||||
|
|
||||||
if (cacheKey in this.cachedDatasourcePromise) {
|
if (cacheKey in this.cachedDataSourcePromise) {
|
||||||
return this.cachedDatasourcePromise[cacheKey];
|
return this.cachedDataSourcePromise[cacheKey];
|
||||||
}
|
}
|
||||||
|
|
||||||
const creationPromise = (async (): Promise<WorkspaceDataSource> => {
|
const creationPromise = (async (): Promise<WorkspaceDataSource> => {
|
||||||
try {
|
try {
|
||||||
const result = await this.cacheManager.execute(
|
const result = await this.cacheManager.execute(
|
||||||
cacheKey as '`${string}-${string}`',
|
cacheKey,
|
||||||
async () => {
|
async () => {
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
`Creating workspace data source for workspace ${workspaceId} and metadata version ${cachedWorkspaceMetadataVersion}`,
|
`Creating workspace data source for workspace ${workspaceId} and metadata version ${cachedWorkspaceMetadataVersion}`,
|
||||||
@ -178,22 +182,23 @@ export class WorkspaceDatasourceFactory {
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
} finally {
|
} finally {
|
||||||
delete this.cachedDatasourcePromise[cacheKey];
|
delete this.cachedDataSourcePromise[cacheKey];
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
this.cachedDatasourcePromise[cacheKey] = creationPromise;
|
this.cachedDataSourcePromise[cacheKey] = creationPromise;
|
||||||
|
|
||||||
return creationPromise;
|
return creationPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async destroy(workspaceId: string): Promise<void> {
|
public async destroy(workspaceId: string): Promise<void> {
|
||||||
const cachedWorkspaceMetadataVersion =
|
const cacheKeys = (
|
||||||
await this.workspaceCacheStorageService.getMetadataVersion(workspaceId);
|
Object.keys(this.cachedDataSourcePromise) as CacheKey[]
|
||||||
|
).filter((key) => key.startsWith(`${workspaceId}`));
|
||||||
|
|
||||||
await this.cacheManager.clearKey(
|
for (const cacheKey of cacheKeys) {
|
||||||
`${workspaceId}-${cachedWorkspaceMetadataVersion}`,
|
await this.cacheManager.clearKey(cacheKey);
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getWorkspaceMetadataVersionFromCache(
|
private async getWorkspaceMetadataVersionFromCache(
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { isDefined } from 'twenty-shared';
|
import { isDefined } from 'twenty-shared';
|
||||||
|
|
||||||
type CacheKey = `${string}-${string}`;
|
import { CacheKey } from 'src/engine/twenty-orm/storage/types/cache-key.type';
|
||||||
|
|
||||||
type AsyncFactoryCallback<T> = () => Promise<T | null>;
|
type AsyncFactoryCallback<T> = () => Promise<T | null>;
|
||||||
|
|
||||||
@ -52,6 +52,9 @@ export class CacheManager<T> {
|
|||||||
await onDelete?.(cachedValue);
|
await onDelete?.(cachedValue);
|
||||||
this.cache.delete(cacheKey);
|
this.cache.delete(cacheKey);
|
||||||
}
|
}
|
||||||
|
// TODO: remove this once we have debug on prod
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('Datasource cache size: ', this.cache.size);
|
||||||
}
|
}
|
||||||
|
|
||||||
async clear(onDelete?: (value: T) => Promise<void> | void): Promise<void> {
|
async clear(onDelete?: (value: T) => Promise<void> | void): Promise<void> {
|
||||||
|
|||||||
@ -0,0 +1 @@
|
|||||||
|
export type CacheKey = `${string}-${string}`;
|
||||||
@ -50,8 +50,15 @@ export class TwentyORMGlobalManager {
|
|||||||
return repository;
|
return repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDataSourceForWorkspace(workspaceId: string) {
|
async getDataSourceForWorkspace(
|
||||||
return await this.workspaceDataSourceFactory.create(workspaceId, null);
|
workspaceId: string,
|
||||||
|
failOnMetadataCacheMiss = true,
|
||||||
|
) {
|
||||||
|
return await this.workspaceDataSourceFactory.create(
|
||||||
|
workspaceId,
|
||||||
|
null,
|
||||||
|
failOnMetadataCacheMiss,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async destroyDataSourceForWorkspace(workspaceId: string) {
|
async destroyDataSourceForWorkspace(workspaceId: string) {
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { Repository } from 'typeorm';
|
|||||||
import { ActiveWorkspacesCommandRunner } from 'src/database/commands/active-workspaces.command';
|
import { ActiveWorkspacesCommandRunner } from 'src/database/commands/active-workspaces.command';
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||||
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import { WorkspaceHealthService } from 'src/engine/workspace-manager/workspace-health/workspace-health.service';
|
import { WorkspaceHealthService } from 'src/engine/workspace-manager/workspace-health/workspace-health.service';
|
||||||
import { WorkspaceSyncMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.service';
|
import { WorkspaceSyncMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.service';
|
||||||
|
|
||||||
@ -30,8 +31,9 @@ export class SyncWorkspaceMetadataCommand extends ActiveWorkspacesCommandRunner
|
|||||||
private readonly workspaceHealthService: WorkspaceHealthService,
|
private readonly workspaceHealthService: WorkspaceHealthService,
|
||||||
private readonly dataSourceService: DataSourceService,
|
private readonly dataSourceService: DataSourceService,
|
||||||
private readonly syncWorkspaceLoggerService: SyncWorkspaceLoggerService,
|
private readonly syncWorkspaceLoggerService: SyncWorkspaceLoggerService,
|
||||||
|
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
) {
|
) {
|
||||||
super(workspaceRepository);
|
super(workspaceRepository, twentyORMGlobalManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
async executeActiveWorkspacesCommand(
|
async executeActiveWorkspacesCommand(
|
||||||
|
|||||||
Reference in New Issue
Block a user