feat: standard fields on custom (#4332)

* feat: add ability to sync standard fields on custom object

* fix: clean

* fix: wrong compute during object creation

* fix: missing cascade delete

* fix: remove unused injected class

* fix: naming

* fix: rename factory to paramsFactory and clean

* fix: rename ExtendCustomObjectMetadata to BaseCustomObjectMetadata

* fix: partial fix inconsistent label and description

* Fixes

* Fix

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Jérémy M
2024-03-07 17:21:50 +01:00
committed by GitHub
parent c3a024b047
commit af6ffbcc68
54 changed files with 923 additions and 250 deletions

View File

@ -153,8 +153,8 @@ export class WorkspaceMetadataUpdaterService {
// https://github.com/typeorm/typeorm/issues/3490
// To avoid calling update in a for loop, we did this hack.
return {
...oldFieldMetadata,
...updateFieldMetadata,
...omit(oldFieldMetadata, ['objectMetadataId', 'workspaceId']),
...omit(updateFieldMetadata, ['objectMetadataId', 'workspaceId']),
options: updateFieldMetadata.options ?? oldFieldMetadata.options,
};
});
@ -197,7 +197,11 @@ export class WorkspaceMetadataUpdaterService {
return {
current: oldFieldMetadata as FieldMetadataEntity,
altered: alteredFieldMetadata as FieldMetadataEntity,
altered: {
...alteredFieldMetadata,
objectMetadataId: oldFieldMetadata.objectMetadataId,
workspaceId: oldFieldMetadata.workspaceId,
} as FieldMetadataEntity,
};
},
),

View File

@ -0,0 +1,140 @@
import { Injectable, Logger } from '@nestjs/common';
import { EntityManager } from 'typeorm';
import { WorkspaceSyncContext } from 'src/workspace/workspace-sync-metadata/interfaces/workspace-sync-context.interface';
import { ComparatorAction } from 'src/workspace/workspace-sync-metadata/interfaces/comparator.interface';
import { FeatureFlagMap } from 'src/core/feature-flag/interfaces/feature-flag-map.interface';
import { WorkspaceMigrationBuilderAction } from 'src/workspace/workspace-migration-builder/interfaces/workspace-migration-builder-action.interface';
import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity';
import { WorkspaceMigrationEntity } from 'src/metadata/workspace-migration/workspace-migration.entity';
import { WorkspaceFieldComparator } from 'src/workspace/workspace-sync-metadata/comparators/workspace-field.comparator';
import { WorkspaceMetadataUpdaterService } from 'src/workspace/workspace-sync-metadata/services/workspace-metadata-updater.service';
import { WorkspaceSyncStorage } from 'src/workspace/workspace-sync-metadata/storage/workspace-sync.storage';
import { WorkspaceMigrationFieldFactory } from 'src/workspace/workspace-migration-builder/factories/workspace-migration-field.factory';
import { StandardFieldFactory } from 'src/workspace/workspace-sync-metadata/factories/standard-field.factory';
import { CustomObjectMetadata } from 'src/workspace/workspace-sync-metadata/custom-objects/custom.object-metadata';
import { computeStandardObject } from 'src/workspace/workspace-sync-metadata/utils/compute-standard-object.util';
@Injectable()
export class WorkspaceSyncFieldMetadataService {
private readonly logger = new Logger(WorkspaceSyncFieldMetadataService.name);
constructor(
private readonly standardFieldFactory: StandardFieldFactory,
private readonly workspaceFieldComparator: WorkspaceFieldComparator,
private readonly workspaceMetadataUpdaterService: WorkspaceMetadataUpdaterService,
private readonly workspaceMigrationFieldFactory: WorkspaceMigrationFieldFactory,
) {}
async synchronize(
context: WorkspaceSyncContext,
manager: EntityManager,
storage: WorkspaceSyncStorage,
workspaceFeatureFlagsMap: FeatureFlagMap,
): Promise<Partial<WorkspaceMigrationEntity>[]> {
const objectMetadataRepository =
manager.getRepository(ObjectMetadataEntity);
// Retrieve object metadata collection from DB
const originalObjectMetadataCollection =
await objectMetadataRepository.find({
where: {
workspaceId: context.workspaceId,
// We're only interested in standard fields
fields: { isCustom: false },
},
relations: ['dataSource', 'fields'],
});
// Filter out custom objects
const customObjectMetadataCollection =
originalObjectMetadataCollection.filter(
(objectMetadata) => objectMetadata.isCustom,
);
// Create standard field metadata collection
const standardFieldMetadataCollection = this.standardFieldFactory.create(
CustomObjectMetadata,
context,
workspaceFeatureFlagsMap,
);
// Loop over all standard objects and compare them with the objects in DB
for (const customObjectMetadata of customObjectMetadataCollection) {
// Also, maybe it's better to refactor a bit and move generation part into a separate module ?
const standardObjectMetadata = computeStandardObject(
{
...customObjectMetadata,
fields: standardFieldMetadataCollection,
},
customObjectMetadata,
);
/**
* COMPARE FIELD METADATA
*/
const fieldComparatorResults = this.workspaceFieldComparator.compare(
customObjectMetadata,
standardObjectMetadata,
);
for (const fieldComparatorResult of fieldComparatorResults) {
switch (fieldComparatorResult.action) {
case ComparatorAction.CREATE: {
storage.addCreateFieldMetadata(fieldComparatorResult.object);
break;
}
case ComparatorAction.UPDATE: {
storage.addUpdateFieldMetadata(fieldComparatorResult.object);
break;
}
case ComparatorAction.DELETE: {
storage.addDeleteFieldMetadata(fieldComparatorResult.object);
break;
}
}
}
}
this.logger.log('Updating workspace metadata');
const metadataFieldUpdaterResult =
await this.workspaceMetadataUpdaterService.updateFieldMetadata(
manager,
storage,
);
this.logger.log('Generating migrations');
const createFieldWorkspaceMigrations =
await this.workspaceMigrationFieldFactory.create(
originalObjectMetadataCollection,
metadataFieldUpdaterResult.createdFieldMetadataCollection,
WorkspaceMigrationBuilderAction.CREATE,
);
const updateFieldWorkspaceMigrations =
await this.workspaceMigrationFieldFactory.create(
originalObjectMetadataCollection,
metadataFieldUpdaterResult.updatedFieldMetadataCollection,
WorkspaceMigrationBuilderAction.UPDATE,
);
const deleteFieldWorkspaceMigrations =
await this.workspaceMigrationFieldFactory.create(
originalObjectMetadataCollection,
storage.fieldMetadataDeleteCollection,
WorkspaceMigrationBuilderAction.DELETE,
);
this.logger.log('Saving migrations');
return [
...createFieldWorkspaceMigrations,
...updateFieldWorkspaceMigrations,
...deleteFieldWorkspaceMigrations,
];
}
}

View File

@ -16,7 +16,8 @@ import { WorkspaceFieldComparator } from 'src/workspace/workspace-sync-metadata/
import { WorkspaceMetadataUpdaterService } from 'src/workspace/workspace-sync-metadata/services/workspace-metadata-updater.service';
import { WorkspaceSyncStorage } from 'src/workspace/workspace-sync-metadata/storage/workspace-sync.storage';
import { WorkspaceMigrationObjectFactory } from 'src/workspace/workspace-migration-builder/factories/workspace-migration-object.factory';
import { WorkspaceMigrationFieldFactory } from 'src/workspace/workspace-migration-builder/factories/workspace-migration-field.factory';
import { computeStandardObject } from 'src/workspace/workspace-sync-metadata/utils/compute-standard-object.util';
import { standardObjectMetadataDefinitions } from 'src/workspace/workspace-sync-metadata/standard-objects';
@Injectable()
export class WorkspaceSyncObjectMetadataService {
@ -28,7 +29,6 @@ export class WorkspaceSyncObjectMetadataService {
private readonly workspaceFieldComparator: WorkspaceFieldComparator,
private readonly workspaceMetadataUpdaterService: WorkspaceMetadataUpdaterService,
private readonly workspaceMigrationObjectFactory: WorkspaceMigrationObjectFactory,
private readonly workspaceMigrationFieldFactory: WorkspaceMigrationFieldFactory,
) {}
async synchronize(
@ -45,14 +45,18 @@ export class WorkspaceSyncObjectMetadataService {
await objectMetadataRepository.find({
where: {
workspaceId: context.workspaceId,
isCustom: false,
fields: { isCustom: false },
},
relations: ['dataSource', 'fields'],
});
const customObjectMetadataCollection =
originalObjectMetadataCollection.filter(
(objectMetadata) => objectMetadata.isCustom,
);
// Create standard object metadata collection
const standardObjectMetadataCollection = this.standardObjectFactory.create(
standardObjectMetadataDefinitions,
context,
workspaceFeatureFlagsMap,
);
@ -68,7 +72,9 @@ export class WorkspaceSyncObjectMetadataService {
this.logger.log('Comparing standard objects and fields metadata');
// Store object that need to be deleted
for (const originalObjectMetadata of originalObjectMetadataCollection) {
for (const originalObjectMetadata of originalObjectMetadataCollection.filter(
(object) => !object.isCustom,
)) {
if (!standardObjectMetadataMap[originalObjectMetadata.nameSingular]) {
storage.addDeleteObjectMetadata(originalObjectMetadata);
}
@ -78,8 +84,11 @@ export class WorkspaceSyncObjectMetadataService {
for (const standardObjectName in standardObjectMetadataMap) {
const originalObjectMetadata =
originalObjectMetadataMap[standardObjectName];
const standardObjectMetadata =
standardObjectMetadataMap[standardObjectName];
const standardObjectMetadata = computeStandardObject(
standardObjectMetadataMap[standardObjectName],
originalObjectMetadata,
customObjectMetadataCollection,
);
/**
* COMPARE OBJECT METADATA
@ -132,11 +141,6 @@ export class WorkspaceSyncObjectMetadataService {
manager,
storage,
);
const metadataFieldUpdaterResult =
await this.workspaceMetadataUpdaterService.updateFieldMetadata(
manager,
storage,
);
this.logger.log('Generating migrations');
@ -153,35 +157,11 @@ export class WorkspaceSyncObjectMetadataService {
WorkspaceMigrationBuilderAction.DELETE,
);
const createFieldWorkspaceMigrations =
await this.workspaceMigrationFieldFactory.create(
originalObjectMetadataCollection,
metadataFieldUpdaterResult.createdFieldMetadataCollection,
WorkspaceMigrationBuilderAction.CREATE,
);
const updateFieldWorkspaceMigrations =
await this.workspaceMigrationFieldFactory.create(
originalObjectMetadataCollection,
metadataFieldUpdaterResult.updatedFieldMetadataCollection,
WorkspaceMigrationBuilderAction.UPDATE,
);
const deleteFieldWorkspaceMigrations =
await this.workspaceMigrationFieldFactory.create(
originalObjectMetadataCollection,
storage.fieldMetadataDeleteCollection,
WorkspaceMigrationBuilderAction.DELETE,
);
this.logger.log('Saving migrations');
return [
...createObjectWorkspaceMigrations,
...deleteObjectWorkspaceMigrations,
...createFieldWorkspaceMigrations,
...updateFieldWorkspaceMigrations,
...deleteFieldWorkspaceMigrations,
];
}
}

View File

@ -16,6 +16,8 @@ import { WorkspaceMetadataUpdaterService } from 'src/workspace/workspace-sync-me
import { WorkspaceMigrationEntity } from 'src/metadata/workspace-migration/workspace-migration.entity';
import { WorkspaceSyncStorage } from 'src/workspace/workspace-sync-metadata/storage/workspace-sync.storage';
import { WorkspaceMigrationRelationFactory } from 'src/workspace/workspace-migration-builder/factories/workspace-migration-relation.factory';
import { standardObjectMetadataDefinitions } from 'src/workspace/workspace-sync-metadata/standard-objects';
import { CustomObjectMetadata } from 'src/workspace/workspace-sync-metadata/custom-objects/custom.object-metadata';
@Injectable()
export class WorkspaceSyncRelationMetadataService {
@ -44,11 +46,14 @@ export class WorkspaceSyncRelationMetadataService {
await objectMetadataRepository.find({
where: {
workspaceId: context.workspaceId,
isCustom: false,
fields: { isCustom: false },
},
relations: ['dataSource', 'fields'],
});
const customObjectMetadataCollection =
originalObjectMetadataCollection.filter(
(objectMetadata) => objectMetadata.isCustom,
);
// Create map of object metadata & field metadata by unique identifier
const originalObjectMetadataMap = mapObjectMetadataByUniqueIdentifier(
@ -71,6 +76,18 @@ export class WorkspaceSyncRelationMetadataService {
// Create standard relation metadata collection
const standardRelationMetadataCollection =
this.standardRelationFactory.create(
standardObjectMetadataDefinitions,
context,
originalObjectMetadataMap,
workspaceFeatureFlagsMap,
);
const customRelationMetadataCollection =
this.standardRelationFactory.create(
customObjectMetadataCollection.map((objectMetadata) => ({
object: objectMetadata,
metadata: CustomObjectMetadata,
})),
context,
originalObjectMetadataMap,
workspaceFeatureFlagsMap,
@ -78,7 +95,10 @@ export class WorkspaceSyncRelationMetadataService {
const relationComparatorResults = this.workspaceRelationComparator.compare(
originalRelationMetadataCollection,
standardRelationMetadataCollection,
[
...standardRelationMetadataCollection,
...customRelationMetadataCollection,
],
);
for (const relationComparatorResult of relationComparatorResults) {