mirror of
				https://github.com/lingble/twenty.git
				synced 2025-10-29 11:52:28 +00:00 
			
		
		
		
	feat: add dry-run option to sync-metadata command (#3758)
* feat: add dry-run option to sync-metadata command * feat: save metadata logs in dry-run mode
This commit is contained in:
		
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -17,4 +17,6 @@ | ||||
| !.yarn/sdks | ||||
| !.yarn/versions | ||||
| coverage | ||||
| .vercel | ||||
| .vercel | ||||
|  | ||||
| **/**/logs/** | ||||
| @@ -6,6 +6,7 @@ import { WorkspaceSyncMetadataService } from 'src/workspace/workspace-sync-metad | ||||
| // TODO: implement dry-run | ||||
| interface RunWorkspaceMigrationsOptions { | ||||
|   workspaceId: string; | ||||
|   dryRun?: boolean; | ||||
| } | ||||
|  | ||||
| @Command({ | ||||
| @@ -35,6 +36,7 @@ export class SyncWorkspaceMetadataCommand extends CommandRunner { | ||||
|         workspaceId: options.workspaceId, | ||||
|         dataSourceId: dataSourceMetadata.id, | ||||
|       }, | ||||
|       { dryRun: options.dryRun }, | ||||
|     ); | ||||
|   } | ||||
|  | ||||
| @@ -46,4 +48,13 @@ export class SyncWorkspaceMetadataCommand extends CommandRunner { | ||||
|   parseWorkspaceId(value: string): string { | ||||
|     return value; | ||||
|   } | ||||
|  | ||||
|   @Option({ | ||||
|     flags: '-d, --dry-run', | ||||
|     description: 'Dry run without applying changes', | ||||
|     required: false, | ||||
|   }) | ||||
|   dryRun(): boolean { | ||||
|     return true; | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,70 @@ | ||||
| import { Injectable } from '@nestjs/common'; | ||||
|  | ||||
| import fs from 'fs/promises'; | ||||
|  | ||||
| import { WorkspaceSyncStorage } from 'src/workspace/workspace-sync-metadata/storage/workspace-sync.storage'; | ||||
| import { WorkspaceMigrationEntity } from 'src/metadata/workspace-migration/workspace-migration.entity'; | ||||
|  | ||||
| @Injectable() | ||||
| export class WorkspaceLogsService { | ||||
|   constructor() {} | ||||
|  | ||||
|   async saveLogs( | ||||
|     storage: WorkspaceSyncStorage, | ||||
|     workspaceMigrations: WorkspaceMigrationEntity[], | ||||
|   ) { | ||||
|     // Save workspace migrations | ||||
|     await fs.writeFile( | ||||
|       './logs/workspace-migrations.json', | ||||
|       JSON.stringify(workspaceMigrations, null, 2), | ||||
|     ); | ||||
|  | ||||
|     // Save object metadata create collection | ||||
|     await fs.writeFile( | ||||
|       './logs/object-metadata-create-collection.json', | ||||
|       JSON.stringify(storage.objectMetadataCreateCollection, null, 2), | ||||
|     ); | ||||
|  | ||||
|     // Save object metadata update collection | ||||
|     await fs.writeFile( | ||||
|       './logs/object-metadata-update-collection.json', | ||||
|       JSON.stringify(storage.objectMetadataUpdateCollection, null, 2), | ||||
|     ); | ||||
|  | ||||
|     // Save object metadata delete collection | ||||
|     await fs.writeFile( | ||||
|       './logs/object-metadata-delete-collection.json', | ||||
|       JSON.stringify(storage.objectMetadataDeleteCollection, null, 2), | ||||
|     ); | ||||
|  | ||||
|     // Save field metadata create collection | ||||
|     await fs.writeFile( | ||||
|       './logs/field-metadata-create-collection.json', | ||||
|       JSON.stringify(storage.fieldMetadataCreateCollection, null, 2), | ||||
|     ); | ||||
|  | ||||
|     // Save field metadata update collection | ||||
|     await fs.writeFile( | ||||
|       './logs/field-metadata-update-collection.json', | ||||
|       JSON.stringify(storage.fieldMetadataUpdateCollection, null, 2), | ||||
|     ); | ||||
|  | ||||
|     // Save field metadata delete collection | ||||
|     await fs.writeFile( | ||||
|       './logs/field-metadata-delete-collection.json', | ||||
|       JSON.stringify(storage.fieldMetadataDeleteCollection, null, 2), | ||||
|     ); | ||||
|  | ||||
|     // Save relation metadata create collection | ||||
|     await fs.writeFile( | ||||
|       './logs/relation-metadata-create-collection.json', | ||||
|       JSON.stringify(storage.relationMetadataCreateCollection, null, 2), | ||||
|     ); | ||||
|  | ||||
|     // Save relation metadata delete collection | ||||
|     await fs.writeFile( | ||||
|       './logs/relation-metadata-delete-collection.json', | ||||
|       JSON.stringify(storage.relationMetadataDeleteCollection, null, 2), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @@ -33,7 +33,7 @@ export class WorkspaceSyncObjectMetadataService { | ||||
|     manager: EntityManager, | ||||
|     storage: WorkspaceSyncStorage, | ||||
|     workspaceFeatureFlagsMap: FeatureFlagMap, | ||||
|   ): Promise<void> { | ||||
|   ): Promise<WorkspaceMigrationEntity[]> { | ||||
|     const objectMetadataRepository = | ||||
|       manager.getRepository(ObjectMetadataEntity); | ||||
|     const workspaceMigrationRepository = manager.getRepository( | ||||
| @@ -153,6 +153,10 @@ export class WorkspaceSyncObjectMetadataService { | ||||
|     this.logger.log('Saving migrations'); | ||||
|  | ||||
|     // Save migrations into DB | ||||
|     await workspaceMigrationRepository.save(workspaceObjectMigrations); | ||||
|     const workspaceMigrations = await workspaceMigrationRepository.save( | ||||
|       workspaceObjectMigrations, | ||||
|     ); | ||||
|  | ||||
|     return workspaceMigrations; | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -34,7 +34,7 @@ export class WorkspaceSyncRelationMetadataService { | ||||
|     manager: EntityManager, | ||||
|     storage: WorkspaceSyncStorage, | ||||
|     workspaceFeatureFlagsMap: FeatureFlagMap, | ||||
|   ): Promise<void> { | ||||
|   ): Promise<WorkspaceMigrationEntity[]> { | ||||
|     const objectMetadataRepository = | ||||
|       manager.getRepository(ObjectMetadataEntity); | ||||
|     const workspaceMigrationRepository = manager.getRepository( | ||||
| @@ -106,6 +106,10 @@ export class WorkspaceSyncRelationMetadataService { | ||||
|       ); | ||||
|  | ||||
|     // Save migrations into DB | ||||
|     await workspaceMigrationRepository.save(workspaceRelationMigrations); | ||||
|     const workspaceMigrations = await workspaceMigrationRepository.save( | ||||
|       workspaceRelationMigrations, | ||||
|     ); | ||||
|  | ||||
|     return workspaceMigrations; | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -14,6 +14,7 @@ import { workspaceSyncMetadataComparators } from 'src/workspace/workspace-sync-m | ||||
| import { WorkspaceMetadataUpdaterService } from 'src/workspace/workspace-sync-metadata/services/workspace-metadata-updater.service'; | ||||
| import { WorkspaceSyncObjectMetadataService } from 'src/workspace/workspace-sync-metadata/services/workspace-sync-object-metadata.service'; | ||||
| import { WorkspaceSyncRelationMetadataService } from 'src/workspace/workspace-sync-metadata/services/workspace-sync-relation-metadata.service'; | ||||
| import { WorkspaceLogsService } from 'src/workspace/workspace-sync-metadata/services/workspace-logs.service'; | ||||
|  | ||||
| @Module({ | ||||
|   imports: [ | ||||
| @@ -37,6 +38,7 @@ import { WorkspaceSyncRelationMetadataService } from 'src/workspace/workspace-sy | ||||
|     WorkspaceSyncObjectMetadataService, | ||||
|     WorkspaceSyncRelationMetadataService, | ||||
|     WorkspaceSyncMetadataService, | ||||
|     WorkspaceLogsService, | ||||
|   ], | ||||
|   exports: [WorkspaceSyncMetadataService], | ||||
| }) | ||||
|   | ||||
| @@ -10,6 +10,7 @@ import { FeatureFlagFactory } from 'src/workspace/workspace-sync-metadata/factor | ||||
| import { WorkspaceSyncObjectMetadataService } from 'src/workspace/workspace-sync-metadata/services/workspace-sync-object-metadata.service'; | ||||
| import { WorkspaceSyncRelationMetadataService } from 'src/workspace/workspace-sync-metadata/services/workspace-sync-relation-metadata.service'; | ||||
| import { WorkspaceSyncStorage } from 'src/workspace/workspace-sync-metadata/storage/workspace-sync.storage'; | ||||
| import { WorkspaceLogsService } from 'src/workspace/workspace-sync-metadata/services/workspace-logs.service'; | ||||
|  | ||||
| @Injectable() | ||||
| export class WorkspaceSyncMetadataService { | ||||
| @@ -22,6 +23,7 @@ export class WorkspaceSyncMetadataService { | ||||
|     private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService, | ||||
|     private readonly workspaceSyncObjectMetadataService: WorkspaceSyncObjectMetadataService, | ||||
|     private readonly workspaceSyncRelationMetadataService: WorkspaceSyncRelationMetadataService, | ||||
|     private readonly workspaceLogsService: WorkspaceLogsService, | ||||
|   ) {} | ||||
|  | ||||
|   /** | ||||
| @@ -34,6 +36,7 @@ export class WorkspaceSyncMetadataService { | ||||
|    */ | ||||
|   public async syncStandardObjectsAndFieldsMetadata( | ||||
|     context: WorkspaceSyncContext, | ||||
|     options?: { dryRun?: boolean }, | ||||
|   ) { | ||||
|     this.logger.log('Syncing standard objects and fields metadata'); | ||||
|     const queryRunner = this.metadataDataSource.createQueryRunner(); | ||||
| @@ -52,19 +55,37 @@ export class WorkspaceSyncMetadataService { | ||||
|  | ||||
|       this.logger.log('Syncing standard objects and fields metadata'); | ||||
|  | ||||
|       await this.workspaceSyncObjectMetadataService.synchronize( | ||||
|         context, | ||||
|         manager, | ||||
|         storage, | ||||
|         workspaceFeatureFlagsMap, | ||||
|       ); | ||||
|       const workspaceObjectMigrations = | ||||
|         await this.workspaceSyncObjectMetadataService.synchronize( | ||||
|           context, | ||||
|           manager, | ||||
|           storage, | ||||
|           workspaceFeatureFlagsMap, | ||||
|         ); | ||||
|  | ||||
|       await this.workspaceSyncRelationMetadataService.synchronize( | ||||
|         context, | ||||
|         manager, | ||||
|         storage, | ||||
|         workspaceFeatureFlagsMap, | ||||
|       ); | ||||
|       const workspaceRelationMigrations = | ||||
|         await this.workspaceSyncRelationMetadataService.synchronize( | ||||
|           context, | ||||
|           manager, | ||||
|           storage, | ||||
|           workspaceFeatureFlagsMap, | ||||
|         ); | ||||
|  | ||||
|       // If we're running a dry run, rollback the transaction and do not execute migrations | ||||
|       if (options?.dryRun) { | ||||
|         const workspaceMigrations = [ | ||||
|           ...workspaceObjectMigrations, | ||||
|           ...workspaceRelationMigrations, | ||||
|         ]; | ||||
|  | ||||
|         this.logger.log('Running in dry run mode, rolling back transaction'); | ||||
|  | ||||
|         await queryRunner.rollbackTransaction(); | ||||
|  | ||||
|         await this.workspaceLogsService.saveLogs(storage, workspaceMigrations); | ||||
|  | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       await queryRunner.commitTransaction(); | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Jérémy M
					Jérémy M