3491 launch cleaning cron (#3872)
* Add command to delete incomplete workspaces * Inject command dependencies * Fix command * Do not delete core.workspace * Reorganize files * Delete src/workspace/cron * Fix --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -2,12 +2,8 @@ import { Module } from '@nestjs/common';
|
||||
|
||||
import { DatabaseCommandModule } from 'src/database/commands/database-command.module';
|
||||
import { FetchWorkspaceMessagesCommandsModule } from 'src/workspace/messaging/commands/fetch-workspace-messages-commands.module';
|
||||
import { StartCleanInactiveWorkspacesCronCommand } from 'src/workspace/cron/clean-inactive-workspaces/commands/start-clean-inactive-workspaces.cron.command';
|
||||
import { StopCleanInactiveWorkspacesCronCommand } from 'src/workspace/cron/clean-inactive-workspaces/commands/stop-clean-inactive-workspaces.cron.command';
|
||||
import { CleanInactiveWorkspacesCommand } from 'src/workspace/cron/clean-inactive-workspaces/commands/clean-inactive-workspaces.command';
|
||||
import { WorkspaceHealthCommandModule } from 'src/workspace/workspace-health/commands/workspace-health-command.module';
|
||||
import { StartFetchAllWorkspacesMessagesCronCommand } from 'src/workspace/cron/fetch-all-workspaces-messages/commands/start-fetch-all-workspaces-messages.cron.command';
|
||||
import { StopFetchAllWorkspacesMessagesCronCommand } from 'src/workspace/cron/fetch-all-workspaces-messages/commands/stop-fetch-all-workspaces-messages.cron.command';
|
||||
import { WorkspaceCleanerModule } from 'src/workspace/workspace-cleaner/workspace-cleaner.module';
|
||||
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
@ -20,13 +16,9 @@ import { WorkspaceMigrationRunnerCommandsModule } from './workspace/workspace-mi
|
||||
WorkspaceSyncMetadataCommandsModule,
|
||||
DatabaseCommandModule,
|
||||
FetchWorkspaceMessagesCommandsModule,
|
||||
StartCleanInactiveWorkspacesCronCommand,
|
||||
StopCleanInactiveWorkspacesCronCommand,
|
||||
CleanInactiveWorkspacesCommand,
|
||||
WorkspaceCleanerModule,
|
||||
WorkspaceHealthCommandModule,
|
||||
WorkspaceMigrationRunnerCommandsModule,
|
||||
StartFetchAllWorkspacesMessagesCronCommand,
|
||||
StopFetchAllWorkspacesMessagesCronCommand,
|
||||
],
|
||||
})
|
||||
export class CommandModule {}
|
||||
|
||||
@ -17,13 +17,15 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> {
|
||||
super(workspaceRepository);
|
||||
}
|
||||
|
||||
async deleteWorkspace(id: string) {
|
||||
async deleteWorkspace(id: string, shouldDeleteCoreWorkspace = true) {
|
||||
const workspace = await this.workspaceRepository.findOneBy({ id });
|
||||
|
||||
assert(workspace, 'Workspace not found');
|
||||
|
||||
await this.workspaceManagerService.delete(id);
|
||||
await this.workspaceRepository.delete(id);
|
||||
if (shouldDeleteCoreWorkspace) {
|
||||
await this.workspaceRepository.delete(id);
|
||||
}
|
||||
|
||||
return workspace;
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@ import { CallWebhookJob } from 'src/workspace/workspace-query-runner/jobs/call-w
|
||||
import { WorkspaceDataSourceModule } from 'src/workspace/workspace-datasource/workspace-datasource.module';
|
||||
import { ObjectMetadataModule } from 'src/metadata/object-metadata/object-metadata.module';
|
||||
import { DataSourceModule } from 'src/metadata/data-source/data-source.module';
|
||||
import { CleanInactiveWorkspaceJob } from 'src/workspace/cron/clean-inactive-workspaces/clean-inactive-workspace.job';
|
||||
import { CleanInactiveWorkspaceJob } from 'src/workspace/workspace-cleaner/crons/clean-inactive-workspace.job';
|
||||
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
|
||||
import { FetchWorkspaceMessagesModule } from 'src/workspace/messaging/services/fetch-workspace-messages.module';
|
||||
import { GmailPartialSyncJob } from 'src/workspace/messaging/jobs/gmail-partial-sync.job';
|
||||
@ -17,7 +17,7 @@ import { EmailSenderJob } from 'src/integrations/email/email-sender.job';
|
||||
import { UserModule } from 'src/core/user/user.module';
|
||||
import { EnvironmentModule } from 'src/integrations/environment/environment.module';
|
||||
import { FeatureFlagEntity } from 'src/core/feature-flag/feature-flag.entity';
|
||||
import { FetchAllWorkspacesMessagesJob } from 'src/workspace/cron/fetch-all-workspaces-messages/fetch-all-workspaces-messages.job';
|
||||
import { FetchAllWorkspacesMessagesJob } from 'src/workspace/messaging/crons/fetch-all-workspaces-messages.job';
|
||||
import { ConnectedAccountModule } from 'src/workspace/messaging/connected-account/connected-account.module';
|
||||
|
||||
@Module({
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
export const getDryRunLogHeader = (isDryRun: boolean | undefined): string => {
|
||||
return isDryRun ? 'Dry-run mode: ' : '';
|
||||
};
|
||||
@ -7,6 +7,8 @@ import { DataSourceModule } from 'src/metadata/data-source/data-source.module';
|
||||
import { GmailFullSyncCommand } from 'src/workspace/messaging/commands/gmail-full-sync.command';
|
||||
import { GmailPartialSyncCommand } from 'src/workspace/messaging/commands/gmail-partial-sync.command';
|
||||
import { ConnectedAccountModule } from 'src/workspace/messaging/connected-account/connected-account.module';
|
||||
import { StartFetchAllWorkspacesMessagesCronCommand } from 'src/workspace/messaging/commands/start-fetch-all-workspaces-messages.cron.command';
|
||||
import { StopFetchAllWorkspacesMessagesCronCommand } from 'src/workspace/messaging/commands/stop-fetch-all-workspaces-messages.cron.command';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@ -15,6 +17,11 @@ import { ConnectedAccountModule } from 'src/workspace/messaging/connected-accoun
|
||||
TypeOrmModule.forFeature([FeatureFlagEntity], 'core'),
|
||||
ConnectedAccountModule,
|
||||
],
|
||||
providers: [GmailFullSyncCommand, GmailPartialSyncCommand],
|
||||
providers: [
|
||||
GmailFullSyncCommand,
|
||||
GmailPartialSyncCommand,
|
||||
StartFetchAllWorkspacesMessagesCronCommand,
|
||||
StopFetchAllWorkspacesMessagesCronCommand,
|
||||
],
|
||||
})
|
||||
export class FetchWorkspaceMessagesCommandsModule {}
|
||||
|
||||
@ -4,8 +4,8 @@ import { Command, CommandRunner } from 'nest-commander';
|
||||
|
||||
import { MessageQueue } from 'src/integrations/message-queue/message-queue.constants';
|
||||
import { MessageQueueService } from 'src/integrations/message-queue/services/message-queue.service';
|
||||
import { fetchAllWorkspacesMessagesCronPattern } from 'src/workspace/cron/fetch-all-workspaces-messages/fetch-all-workspaces-messages.cron.pattern';
|
||||
import { FetchAllWorkspacesMessagesJob } from 'src/workspace/cron/fetch-all-workspaces-messages/fetch-all-workspaces-messages.job';
|
||||
import { fetchAllWorkspacesMessagesCronPattern } from 'src/workspace/messaging/crons/fetch-all-workspaces-messages.cron.pattern';
|
||||
import { FetchAllWorkspacesMessagesJob } from 'src/workspace/messaging/crons/fetch-all-workspaces-messages.job';
|
||||
|
||||
@Command({
|
||||
name: 'fetch-all-workspaces-messages:cron:start',
|
||||
@ -4,8 +4,8 @@ import { Command, CommandRunner } from 'nest-commander';
|
||||
|
||||
import { MessageQueue } from 'src/integrations/message-queue/message-queue.constants';
|
||||
import { MessageQueueService } from 'src/integrations/message-queue/services/message-queue.service';
|
||||
import { fetchAllWorkspacesMessagesCronPattern } from 'src/workspace/cron/fetch-all-workspaces-messages/fetch-all-workspaces-messages.cron.pattern';
|
||||
import { FetchAllWorkspacesMessagesJob } from 'src/workspace/cron/fetch-all-workspaces-messages/fetch-all-workspaces-messages.job';
|
||||
import { fetchAllWorkspacesMessagesCronPattern } from 'src/workspace/messaging/crons/fetch-all-workspaces-messages.cron.pattern';
|
||||
import { FetchAllWorkspacesMessagesJob } from 'src/workspace/messaging/crons/fetch-all-workspaces-messages.job';
|
||||
|
||||
@Command({
|
||||
name: 'fetch-all-workspaces-messages:cron:stop',
|
||||
@ -4,7 +4,7 @@ import { Command, CommandRunner, Option } from 'nest-commander';
|
||||
|
||||
import { MessageQueue } from 'src/integrations/message-queue/message-queue.constants';
|
||||
import { MessageQueueService } from 'src/integrations/message-queue/services/message-queue.service';
|
||||
import { CleanInactiveWorkspaceJob } from 'src/workspace/cron/clean-inactive-workspaces/clean-inactive-workspace.job';
|
||||
import { CleanInactiveWorkspaceJob } from 'src/workspace/workspace-cleaner/crons/clean-inactive-workspace.job';
|
||||
|
||||
export type CleanInactiveWorkspacesCommandOptions = {
|
||||
dryRun: boolean;
|
||||
@ -0,0 +1,75 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Logger } from '@nestjs/common';
|
||||
|
||||
import { Command, CommandRunner, Option } from 'nest-commander';
|
||||
import { FindOptionsWhere, Repository } from 'typeorm';
|
||||
|
||||
import { WorkspaceService } from 'src/core/workspace/services/workspace.service';
|
||||
import { Workspace } from 'src/core/workspace/workspace.entity';
|
||||
import { getDryRunLogHeader } from 'src/utils/get-dry-run-log-header';
|
||||
|
||||
type DeleteIncompleteWorkspacesCommandOptions = {
|
||||
dryRun?: boolean;
|
||||
workspaceId?: string;
|
||||
};
|
||||
|
||||
@Command({
|
||||
name: 'workspace:delete-incomplete',
|
||||
description: 'Delete incomplete workspaces',
|
||||
})
|
||||
export class DeleteIncompleteWorkspacesCommand extends CommandRunner {
|
||||
private readonly logger = new Logger(DeleteIncompleteWorkspacesCommand.name);
|
||||
constructor(
|
||||
private readonly workspaceService: WorkspaceService,
|
||||
@InjectRepository(Workspace, 'core')
|
||||
private readonly workspaceRepository: Repository<Workspace>,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
@Option({
|
||||
flags: '-d, --dry-run [dry run]',
|
||||
description: 'Dry run: Log delete actions without executing them.',
|
||||
required: false,
|
||||
})
|
||||
dryRun(value: string): boolean {
|
||||
return Boolean(value);
|
||||
}
|
||||
|
||||
@Option({
|
||||
flags: '-w, --workspace-id [workspace_id]',
|
||||
description: 'workspace id',
|
||||
required: false,
|
||||
})
|
||||
parseWorkspaceId(value: string): string {
|
||||
return value;
|
||||
}
|
||||
|
||||
async run(
|
||||
_passedParam: string[],
|
||||
options: DeleteIncompleteWorkspacesCommandOptions,
|
||||
): Promise<void> {
|
||||
const where: FindOptionsWhere<Workspace> = {
|
||||
subscriptionStatus: 'incomplete',
|
||||
};
|
||||
|
||||
if (options.workspaceId) {
|
||||
where.id = options.workspaceId;
|
||||
}
|
||||
const incompleteWorkspaces = await this.workspaceRepository.findBy(where);
|
||||
|
||||
for (const incompleteWorkspace of incompleteWorkspaces) {
|
||||
this.logger.log(
|
||||
`${getDryRunLogHeader(options.dryRun)}Deleting workspace ${
|
||||
incompleteWorkspace.id
|
||||
} name: '${incompleteWorkspace.displayName}'`,
|
||||
);
|
||||
if (!options.dryRun) {
|
||||
await this.workspaceService.deleteWorkspace(
|
||||
incompleteWorkspace.id,
|
||||
false,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4,8 +4,8 @@ import { Command, CommandRunner } from 'nest-commander';
|
||||
|
||||
import { MessageQueue } from 'src/integrations/message-queue/message-queue.constants';
|
||||
import { MessageQueueService } from 'src/integrations/message-queue/services/message-queue.service';
|
||||
import { cleanInactiveWorkspaceCronPattern } from 'src/workspace/cron/clean-inactive-workspaces/clean-inactive-workspace.cron.pattern';
|
||||
import { CleanInactiveWorkspaceJob } from 'src/workspace/cron/clean-inactive-workspaces/clean-inactive-workspace.job';
|
||||
import { cleanInactiveWorkspaceCronPattern } from 'src/workspace/workspace-cleaner/crons/clean-inactive-workspace.cron.pattern';
|
||||
import { CleanInactiveWorkspaceJob } from 'src/workspace/workspace-cleaner/crons/clean-inactive-workspace.job';
|
||||
|
||||
@Command({
|
||||
name: 'clean-inactive-workspaces:cron:start',
|
||||
@ -4,8 +4,8 @@ import { Command, CommandRunner } from 'nest-commander';
|
||||
|
||||
import { MessageQueue } from 'src/integrations/message-queue/message-queue.constants';
|
||||
import { MessageQueueService } from 'src/integrations/message-queue/services/message-queue.service';
|
||||
import { cleanInactiveWorkspaceCronPattern } from 'src/workspace/cron/clean-inactive-workspaces/clean-inactive-workspace.cron.pattern';
|
||||
import { CleanInactiveWorkspaceJob } from 'src/workspace/cron/clean-inactive-workspaces/clean-inactive-workspace.job';
|
||||
import { cleanInactiveWorkspaceCronPattern } from 'src/workspace/workspace-cleaner/crons/clean-inactive-workspace.cron.pattern';
|
||||
import { CleanInactiveWorkspaceJob } from 'src/workspace/workspace-cleaner/crons/clean-inactive-workspace.job';
|
||||
|
||||
@Command({
|
||||
name: 'clean-inactive-workspaces:cron:stop',
|
||||
@ -23,7 +23,8 @@ import {
|
||||
} from 'src/core/feature-flag/feature-flag.entity';
|
||||
import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity';
|
||||
import { computeObjectTargetTable } from 'src/workspace/utils/compute-object-target-table.util';
|
||||
import { CleanInactiveWorkspacesCommandOptions } from 'src/workspace/cron/clean-inactive-workspaces/commands/clean-inactive-workspaces.command';
|
||||
import { CleanInactiveWorkspacesCommandOptions } from 'src/workspace/workspace-cleaner/commands/clean-inactive-workspaces.command';
|
||||
import { getDryRunLogHeader } from 'src/utils/get-dry-run-log-header';
|
||||
|
||||
const MILLISECONDS_IN_ONE_DAY = 1000 * 3600 * 24;
|
||||
|
||||
@ -112,7 +113,7 @@ export class CleanInactiveWorkspaceJob
|
||||
)?.[0].displayName;
|
||||
|
||||
this.logger.log(
|
||||
`${this.getDryRunLogHeader(isDryRun)}Sending workspace ${
|
||||
`${getDryRunLogHeader(isDryRun)}Sending workspace ${
|
||||
dataSource.workspaceId
|
||||
} inactive since ${daysSinceInactive} days emails to users ['${workspaceMembers
|
||||
.map((workspaceUser) => workspaceUser.email)
|
||||
@ -162,10 +163,6 @@ export class CleanInactiveWorkspaceJob
|
||||
);
|
||||
}
|
||||
|
||||
getDryRunLogHeader(isDryRun: boolean): string {
|
||||
return isDryRun ? 'Dry-run mode: ' : '';
|
||||
}
|
||||
|
||||
chunkArray(array: any[], chunkSize = 6): any[][] {
|
||||
const chunkedArray: any[][] = [];
|
||||
let index = 0;
|
||||
@ -183,7 +180,7 @@ export class CleanInactiveWorkspaceJob
|
||||
isDryRun: boolean,
|
||||
): Promise<void> {
|
||||
this.logger.log(
|
||||
`${this.getDryRunLogHeader(
|
||||
`${getDryRunLogHeader(
|
||||
isDryRun,
|
||||
)}Sending email to delete workspaces "${workspacesToDelete
|
||||
.map((workspaceToDelete) => workspaceToDelete.workspaceId)
|
||||
@ -216,7 +213,7 @@ export class CleanInactiveWorkspaceJob
|
||||
|
||||
const workspacesToDelete: WorkspaceToDeleteData[] = [];
|
||||
|
||||
this.logger.log(`${this.getDryRunLogHeader(isDryRun)}Job running...`);
|
||||
this.logger.log(`${getDryRunLogHeader(isDryRun)}Job running...`);
|
||||
if (!this.inactiveDaysBeforeDelete && !this.inactiveDaysBeforeEmail) {
|
||||
this.logger.log(
|
||||
`'WORKSPACE_INACTIVE_DAYS_BEFORE_NOTIFICATION' and 'WORKSPACE_INACTIVE_DAYS_BEFORE_DELETION' environment variables not set, please check this doc for more info: https://docs.twenty.com/start/self-hosting/environment-variables`,
|
||||
@ -231,7 +228,7 @@ export class CleanInactiveWorkspaceJob
|
||||
const dataSourcesChunks = this.chunkArray(dataSources);
|
||||
|
||||
this.logger.log(
|
||||
`${this.getDryRunLogHeader(isDryRun)}On ${
|
||||
`${getDryRunLogHeader(isDryRun)}On ${
|
||||
dataSources.length
|
||||
} workspaces divided in ${dataSourcesChunks.length} chunks...`,
|
||||
);
|
||||
@ -246,7 +243,7 @@ export class CleanInactiveWorkspaceJob
|
||||
for (const dataSource of dataSourcesChunk) {
|
||||
if (!(await this.isWorkspaceCleanable(dataSource))) {
|
||||
this.logger.log(
|
||||
`${this.getDryRunLogHeader(isDryRun)}Workspace ${
|
||||
`${getDryRunLogHeader(isDryRun)}Workspace ${
|
||||
dataSource.workspaceId
|
||||
} not cleanable`,
|
||||
);
|
||||
@ -254,7 +251,7 @@ export class CleanInactiveWorkspaceJob
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`${this.getDryRunLogHeader(isDryRun)}Cleaning Workspace ${
|
||||
`${getDryRunLogHeader(isDryRun)}Cleaning Workspace ${
|
||||
dataSource.workspaceId
|
||||
}`,
|
||||
);
|
||||
@ -285,6 +282,6 @@ export class CleanInactiveWorkspaceJob
|
||||
|
||||
await this.sendDeleteWorkspaceEmail(workspacesToDelete, isDryRun);
|
||||
|
||||
this.logger.log(`${this.getDryRunLogHeader(isDryRun)}job done!`);
|
||||
this.logger.log(`${getDryRunLogHeader(isDryRun)}job done!`);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { WorkspaceModule } from 'src/core/workspace/workspace.module';
|
||||
import { DeleteIncompleteWorkspacesCommand } from 'src/workspace/workspace-cleaner/commands/delete-incomplete-workspaces.command';
|
||||
import { Workspace } from 'src/core/workspace/workspace.entity';
|
||||
import { CleanInactiveWorkspacesCommand } from 'src/workspace/workspace-cleaner/commands/clean-inactive-workspaces.command';
|
||||
import { StartCleanInactiveWorkspacesCronCommand } from 'src/workspace/workspace-cleaner/commands/start-clean-inactive-workspaces.cron.command';
|
||||
import { StopCleanInactiveWorkspacesCronCommand } from 'src/workspace/workspace-cleaner/commands/stop-clean-inactive-workspaces.cron.command';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Workspace], 'core'), WorkspaceModule],
|
||||
providers: [
|
||||
DeleteIncompleteWorkspacesCommand,
|
||||
CleanInactiveWorkspacesCommand,
|
||||
StartCleanInactiveWorkspacesCronCommand,
|
||||
StopCleanInactiveWorkspacesCronCommand,
|
||||
],
|
||||
})
|
||||
export class WorkspaceCleanerModule {}
|
||||
@ -38,7 +38,6 @@ const generateOpportunities = (
|
||||
amountCurrencyCode: 'USD',
|
||||
closeDate: new Date(),
|
||||
stage: getRandomStage(),
|
||||
position: null,
|
||||
probability: getRandomProbability(),
|
||||
pipelineStepId: getRandomPipelineStepId(pipelineStepIds),
|
||||
pointOfContactId: company.personId,
|
||||
|
||||
Reference in New Issue
Block a user