feat: refactor workspace sync fields (#6069)

This PR was first here to fix the issue related to ticket #5004, after
some testing it seems that changing the name of a relation is actually
properly working, if we rename `ONE-TO-MANY` side, the only things that
is going to be updated is the FieldMetadata as the `joinColumn` is
stored on the opposite object.
For `MANY-TO-ONE` relations, the `joinColumn` migration is properly
generated. We need to take care that if we rename a side of a relation,
sometimes the opposite side doesn't have `inverseSideFieldKey`
implemented and used by default the name of the opposite object, so this
is going to throw an error as the field can't be found in the object.

---------

Co-authored-by: Marie <51697796+ijreilly@users.noreply.github.com>
This commit is contained in:
Jérémy M
2024-07-02 17:21:13 +02:00
committed by GitHub
parent a163ccced6
commit 5b26452649
15 changed files with 229 additions and 149 deletions

View File

@ -47,9 +47,6 @@ export class WorkspaceMetadataUpdaterService {
storage.objectMetadataCreateCollection.map((objectMetadata) => ({
...objectMetadata,
isActive: true,
fields: objectMetadata.fields.map((field) =>
this.prepareFieldMetadataForCreation(field),
),
})) as DeepPartial<ObjectMetadataEntity>[],
);
const identifiers = createdPartialObjectMetadataCollection.map(

View File

@ -15,7 +15,9 @@ import { WorkspaceSyncStorage } from 'src/engine/workspace-manager/workspace-syn
import { WorkspaceMigrationFieldFactory } from 'src/engine/workspace-manager/workspace-migration-builder/factories/workspace-migration-field.factory';
import { StandardFieldFactory } from 'src/engine/workspace-manager/workspace-sync-metadata/factories/standard-field.factory';
import { CustomWorkspaceEntity } from 'src/engine/twenty-orm/custom.workspace-entity';
import { computeStandardObject } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/compute-standard-object.util';
import { computeStandardFields } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/compute-standard-fields.util';
import { standardObjectMetadataDefinitions } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-objects';
import { mapObjectMetadataByUniqueIdentifier } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/sync-metadata.util';
@Injectable()
export class WorkspaceSyncFieldMetadataService {
@ -47,56 +49,25 @@ export class WorkspaceSyncFieldMetadataService {
},
relations: ['dataSource', 'fields'],
});
// Filter out non-custom objects
const customObjectMetadataCollection =
originalObjectMetadataCollection.filter(
(objectMetadata) => objectMetadata.isCustom,
);
// Create standard field metadata collection
const standardFieldMetadataCollection = this.standardFieldFactory.create(
CustomWorkspaceEntity,
await this.synchronizeStandardObjectFields(
context,
originalObjectMetadataCollection,
customObjectMetadataCollection,
storage,
workspaceFeatureFlagsMap,
);
// Loop over all custom objects from the DB and compare their fields with standard fields
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;
}
}
}
}
await this.synchronizeCustomObjectFields(
context,
customObjectMetadataCollection,
storage,
workspaceFeatureFlagsMap,
);
this.logger.log('Updating workspace metadata');
@ -137,4 +108,120 @@ export class WorkspaceSyncFieldMetadataService {
...deleteFieldWorkspaceMigrations,
];
}
/**
* This can be optimized to avoid import of standardObjectFactory here.
* We should refactor the logic of the factory, so this one only create the objects and not the fields.
* Then standardFieldFactory should be used to create the fields of standard objects.
*/
private async synchronizeStandardObjectFields(
context: WorkspaceSyncContext,
originalObjectMetadataCollection: ObjectMetadataEntity[],
customObjectMetadataCollection: ObjectMetadataEntity[],
storage: WorkspaceSyncStorage,
workspaceFeatureFlagsMap: FeatureFlagMap,
): Promise<void> {
// Create standard field metadata map
const standardObjectStandardFieldMetadataMap =
this.standardFieldFactory.create(
standardObjectMetadataDefinitions,
context,
workspaceFeatureFlagsMap,
);
// Create map of original and standard object metadata by standard ids
const originalObjectMetadataMap = mapObjectMetadataByUniqueIdentifier(
originalObjectMetadataCollection,
);
this.logger.log('Comparing standard objects and fields metadata');
// Loop over all standard objects and compare them with the objects in DB
for (const [
standardObjectId,
standardFieldMetadataCollection,
] of standardObjectStandardFieldMetadataMap) {
const originalObjectMetadata =
originalObjectMetadataMap[standardObjectId];
const computedStandardFieldMetadataCollection = computeStandardFields(
standardFieldMetadataCollection,
originalObjectMetadata,
// We need to provide this for generated relations with custom objects
customObjectMetadataCollection,
);
const fieldComparatorResults = this.workspaceFieldComparator.compare(
originalObjectMetadata.id,
originalObjectMetadata.fields,
computedStandardFieldMetadataCollection,
);
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;
}
}
}
}
}
private async synchronizeCustomObjectFields(
context: WorkspaceSyncContext,
customObjectMetadataCollection: ObjectMetadataEntity[],
storage: WorkspaceSyncStorage,
workspaceFeatureFlagsMap: FeatureFlagMap,
): Promise<void> {
// Create standard field metadata collection
const customObjectStandardFieldMetadataCollection =
this.standardFieldFactory.create(
CustomWorkspaceEntity,
context,
workspaceFeatureFlagsMap,
);
// Loop over all custom objects from the DB and compare their fields with standard fields
for (const customObjectMetadata of customObjectMetadataCollection) {
// Also, maybe it's better to refactor a bit and move generation part into a separate module ?
const standardFieldMetadataCollection = computeStandardFields(
customObjectStandardFieldMetadataCollection,
customObjectMetadata,
);
/**
* COMPARE FIELD METADATA
*/
const fieldComparatorResults = this.workspaceFieldComparator.compare(
customObjectMetadata.id,
customObjectMetadata.fields,
standardFieldMetadataCollection,
);
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;
}
}
}
}
}
}

View File

@ -12,11 +12,9 @@ import { mapObjectMetadataByUniqueIdentifier } from 'src/engine/workspace-manage
import { WorkspaceMigrationEntity } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
import { StandardObjectFactory } from 'src/engine/workspace-manager/workspace-sync-metadata/factories/standard-object.factory';
import { WorkspaceObjectComparator } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/workspace-object.comparator';
import { WorkspaceFieldComparator } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/workspace-field.comparator';
import { WorkspaceMetadataUpdaterService } from 'src/engine/workspace-manager/workspace-sync-metadata/services/workspace-metadata-updater.service';
import { WorkspaceSyncStorage } from 'src/engine/workspace-manager/workspace-sync-metadata/storage/workspace-sync.storage';
import { WorkspaceMigrationObjectFactory } from 'src/engine/workspace-manager/workspace-migration-builder/factories/workspace-migration-object.factory';
import { computeStandardObject } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/compute-standard-object.util';
import { standardObjectMetadataDefinitions } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-objects';
@Injectable()
@ -26,7 +24,6 @@ export class WorkspaceSyncObjectMetadataService {
constructor(
private readonly standardObjectFactory: StandardObjectFactory,
private readonly workspaceObjectComparator: WorkspaceObjectComparator,
private readonly workspaceFieldComparator: WorkspaceFieldComparator,
private readonly workspaceMetadataUpdaterService: WorkspaceMetadataUpdaterService,
private readonly workspaceMigrationObjectFactory: WorkspaceMigrationObjectFactory,
) {}
@ -49,10 +46,6 @@ export class WorkspaceSyncObjectMetadataService {
},
relations: ['dataSource', 'fields'],
});
const customObjectMetadataCollection =
originalObjectMetadataCollection.filter(
(objectMetadata) => objectMetadata.isCustom,
);
// Create standard object metadata collection
const standardObjectMetadataCollection = this.standardObjectFactory.create(
@ -87,11 +80,8 @@ export class WorkspaceSyncObjectMetadataService {
for (const standardObjectId in standardObjectMetadataMap) {
const originalObjectMetadata =
originalObjectMetadataMap[standardObjectId];
const standardObjectMetadata = computeStandardObject(
standardObjectMetadataMap[standardObjectId],
originalObjectMetadata,
customObjectMetadataCollection,
);
const standardObjectMetadata =
standardObjectMetadataMap[standardObjectId];
/**
* COMPARE OBJECT METADATA
@ -109,35 +99,6 @@ export class WorkspaceSyncObjectMetadataService {
if (objectComparatorResult.action === ComparatorAction.UPDATE) {
storage.addUpdateObjectMetadata(objectComparatorResult.object);
}
/**
* COMPARE FIELD METADATA
* NOTE: This should be moved to WorkspaceSyncFieldMetadataService for more clarity since
* this code only adds field metadata to the storage but it's actually used in the other service.
* NOTE2: WorkspaceSyncFieldMetadataService has been added for custom fields sync, it should be refactored to handle
* both custom and non-custom fields.
*/
const fieldComparatorResults = this.workspaceFieldComparator.compare(
originalObjectMetadata,
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');