feat: new relation sync-metadata, twenty-orm, create/update (#10217)
Fix https://github.com/twentyhq/core-team-issues/issues/330#issue-2827026606 and https://github.com/twentyhq/core-team-issues/issues/327#issue-2827001814 What this PR does when `isNewRelationEnabled` is set to `true`: - [x] Drop the creation of the foreign key as a `FieldMetadata` - [x] Stop creating `RelationMetadata` - [x] Properly fill `FieldMetadata` of type `RELATION` during the sync command - [x] Use new relation settings in TwentyORM - [x] Properly create `FieldMetadata` relations when we create a new object - [x] Handle `database:reset` with new relations --------- Co-authored-by: Charles Bochet <charles@twenty.com> Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com>
This commit is contained in:
@ -93,9 +93,13 @@ export class WorkspaceManagerService {
|
||||
schemaName,
|
||||
);
|
||||
|
||||
const featureFlags =
|
||||
await this.featureFlagService.getWorkspaceFeatureFlagsMap(workspaceId);
|
||||
|
||||
await this.workspaceSyncMetadataService.synchronize({
|
||||
workspaceId,
|
||||
dataSourceId: dataSourceMetadata.id,
|
||||
featureFlags,
|
||||
});
|
||||
|
||||
const dataSourceMetadataCreationEnd = performance.now();
|
||||
@ -145,9 +149,13 @@ export class WorkspaceManagerService {
|
||||
schemaName,
|
||||
);
|
||||
|
||||
const featureFlags =
|
||||
await this.featureFlagService.getWorkspaceFeatureFlagsMap(workspaceId);
|
||||
|
||||
await this.workspaceSyncMetadataService.synchronize({
|
||||
workspaceId,
|
||||
dataSourceId: dataSourceMetadata.id,
|
||||
featureFlags,
|
||||
});
|
||||
|
||||
await this.prefillWorkspaceWithDemoObjects(dataSourceMetadata, workspaceId);
|
||||
@ -165,9 +173,13 @@ export class WorkspaceManagerService {
|
||||
schemaName,
|
||||
);
|
||||
|
||||
const featureFlags =
|
||||
await this.featureFlagService.getWorkspaceFeatureFlagsMap(workspaceId);
|
||||
|
||||
await this.workspaceSyncMetadataService.synchronize({
|
||||
workspaceId: workspaceId,
|
||||
dataSourceId: dataSourceMetadata.id,
|
||||
featureFlags,
|
||||
});
|
||||
|
||||
await this.initPermissionsDev(workspaceId);
|
||||
|
||||
@ -1,12 +1,14 @@
|
||||
import { WorkspaceMigrationIndexFactory } from 'src/engine/workspace-manager/workspace-migration-builder/factories/workspace-migration-index.factory';
|
||||
|
||||
import { WorkspaceMigrationObjectFactory } from './workspace-migration-object.factory';
|
||||
import { WorkspaceMigrationFieldRelationFactory } from './workspace-migration-field-relation.factory';
|
||||
import { WorkspaceMigrationFieldFactory } from './workspace-migration-field.factory';
|
||||
import { WorkspaceMigrationObjectFactory } from './workspace-migration-object.factory';
|
||||
import { WorkspaceMigrationRelationFactory } from './workspace-migration-relation.factory';
|
||||
|
||||
export const workspaceMigrationBuilderFactories = [
|
||||
WorkspaceMigrationObjectFactory,
|
||||
WorkspaceMigrationFieldFactory,
|
||||
WorkspaceMigrationFieldRelationFactory,
|
||||
WorkspaceMigrationRelationFactory,
|
||||
WorkspaceMigrationIndexFactory,
|
||||
];
|
||||
|
||||
@ -0,0 +1,388 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
import { WorkspaceMigrationBuilderAction } from 'src/engine/workspace-manager/workspace-migration-builder/interfaces/workspace-migration-builder-action.interface';
|
||||
|
||||
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 { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util';
|
||||
import {
|
||||
WorkspaceMigrationColumnActionType,
|
||||
WorkspaceMigrationEntity,
|
||||
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 { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
|
||||
import { isFieldMetadataEntityOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
|
||||
import { FieldMetadataUpdate } from 'src/engine/workspace-manager/workspace-migration-builder/factories/workspace-migration-field.factory';
|
||||
import { camelCase } from 'src/utils/camel-case';
|
||||
@Injectable()
|
||||
export class WorkspaceMigrationFieldRelationFactory {
|
||||
constructor(
|
||||
private readonly workspaceMigrationFactory: WorkspaceMigrationFactory,
|
||||
) {}
|
||||
|
||||
async create(
|
||||
originalObjectMetadataCollection: ObjectMetadataEntity[],
|
||||
fieldMetadataCollection: FieldMetadataEntity<FieldMetadataType.RELATION>[],
|
||||
action:
|
||||
| WorkspaceMigrationBuilderAction.CREATE
|
||||
| WorkspaceMigrationBuilderAction.DELETE,
|
||||
): Promise<Partial<WorkspaceMigrationEntity>[]>;
|
||||
|
||||
async create(
|
||||
originalObjectMetadataCollection: ObjectMetadataEntity[],
|
||||
fieldMetadataUpdateCollection: FieldMetadataUpdate<FieldMetadataType.RELATION>[],
|
||||
action: WorkspaceMigrationBuilderAction.UPDATE,
|
||||
): Promise<Partial<WorkspaceMigrationEntity>[]>;
|
||||
|
||||
/**
|
||||
* Deletion of the relation is handled by field deletion
|
||||
*/
|
||||
async create(
|
||||
originalObjectMetadataCollection: ObjectMetadataEntity[],
|
||||
fieldMetadataCollectionOrFieldMetadataUpdateCollection:
|
||||
| FieldMetadataEntity<FieldMetadataType.RELATION>[]
|
||||
| FieldMetadataUpdate<FieldMetadataType.RELATION>[],
|
||||
action: WorkspaceMigrationBuilderAction,
|
||||
): Promise<Partial<WorkspaceMigrationEntity>[]> {
|
||||
const originalObjectMetadataMap = originalObjectMetadataCollection.reduce(
|
||||
(result, currentObject) => {
|
||||
result[currentObject.id] = currentObject;
|
||||
|
||||
return result;
|
||||
},
|
||||
{} as Record<string, ObjectMetadataEntity>,
|
||||
);
|
||||
|
||||
switch (action) {
|
||||
case WorkspaceMigrationBuilderAction.CREATE:
|
||||
return this.createFieldRelationMigration(
|
||||
originalObjectMetadataMap,
|
||||
fieldMetadataCollectionOrFieldMetadataUpdateCollection as FieldMetadataEntity<FieldMetadataType.RELATION>[],
|
||||
);
|
||||
case WorkspaceMigrationBuilderAction.UPDATE:
|
||||
return this.updateFieldRelationMigration(
|
||||
originalObjectMetadataMap,
|
||||
fieldMetadataCollectionOrFieldMetadataUpdateCollection as FieldMetadataUpdate<FieldMetadataType.RELATION>[],
|
||||
);
|
||||
case WorkspaceMigrationBuilderAction.DELETE:
|
||||
return this.deleteFieldRelationMigration(
|
||||
originalObjectMetadataMap,
|
||||
fieldMetadataCollectionOrFieldMetadataUpdateCollection as FieldMetadataEntity<FieldMetadataType.RELATION>[],
|
||||
);
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private async updateFieldRelationMigration(
|
||||
originalObjectMetadataMap: Record<string, ObjectMetadataEntity>,
|
||||
fieldMetadataUpdateCollection: FieldMetadataUpdate<FieldMetadataType.RELATION>[],
|
||||
): Promise<Partial<WorkspaceMigrationEntity>[]> {
|
||||
const workspaceMigrations: Partial<WorkspaceMigrationEntity>[] = [];
|
||||
|
||||
for (const {
|
||||
altered: sourceFieldMetadata,
|
||||
} of fieldMetadataUpdateCollection) {
|
||||
const sourceObjectMetadata =
|
||||
originalObjectMetadataMap[sourceFieldMetadata.objectMetadataId];
|
||||
const targetObjectMetadata =
|
||||
originalObjectMetadataMap[
|
||||
sourceFieldMetadata.relationTargetObjectMetadataId
|
||||
];
|
||||
|
||||
if (!sourceObjectMetadata) {
|
||||
throw new Error(
|
||||
`ObjectMetadata with id ${sourceFieldMetadata.objectMetadataId} not found`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!targetObjectMetadata) {
|
||||
throw new Error(
|
||||
`ObjectMetadata with id ${sourceFieldMetadata.relationTargetObjectMetadataId} not found`,
|
||||
);
|
||||
}
|
||||
|
||||
const targetFieldMetadata = targetObjectMetadata.fields.find(
|
||||
(field) =>
|
||||
field.id === sourceFieldMetadata.relationTargetFieldMetadataId,
|
||||
);
|
||||
|
||||
if (!targetFieldMetadata) {
|
||||
throw new Error(
|
||||
`FieldMetadata with id ${sourceFieldMetadata.relationTargetFieldMetadataId} not found`,
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
!isFieldMetadataEntityOfType(
|
||||
targetFieldMetadata,
|
||||
FieldMetadataType.RELATION,
|
||||
)
|
||||
) {
|
||||
throw new Error(
|
||||
`FieldMetadata with id ${sourceFieldMetadata.relationTargetFieldMetadataId} is not a relation`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!targetFieldMetadata.settings) {
|
||||
throw new Error(
|
||||
`FieldMetadata for relation with id ${sourceFieldMetadata.id} has no settings`,
|
||||
);
|
||||
}
|
||||
|
||||
const migrations: WorkspaceMigrationTableAction[] = [
|
||||
{
|
||||
name: computeObjectTargetTable(targetObjectMetadata),
|
||||
action: WorkspaceMigrationTableActionType.ALTER,
|
||||
columns: [
|
||||
{
|
||||
action: WorkspaceMigrationColumnActionType.DROP_FOREIGN_KEY,
|
||||
columnName: `${camelCase(targetFieldMetadata.name)}Id`,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: computeObjectTargetTable(targetObjectMetadata),
|
||||
action: WorkspaceMigrationTableActionType.ALTER,
|
||||
columns: [
|
||||
{
|
||||
action: WorkspaceMigrationColumnActionType.CREATE_FOREIGN_KEY,
|
||||
columnName: `${camelCase(targetFieldMetadata.name)}Id`,
|
||||
referencedTableName:
|
||||
computeObjectTargetTable(sourceObjectMetadata),
|
||||
referencedTableColumnName: 'id',
|
||||
isUnique:
|
||||
targetFieldMetadata.settings.relationType ===
|
||||
RelationType.ONE_TO_ONE,
|
||||
onDelete: targetFieldMetadata.settings.onDelete,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
workspaceMigrations.push({
|
||||
workspaceId: sourceFieldMetadata.workspaceId,
|
||||
name: generateMigrationName(
|
||||
`update-relation-from-${sourceObjectMetadata.nameSingular}-to-${targetObjectMetadata.nameSingular}`,
|
||||
),
|
||||
isCustom: false,
|
||||
migrations,
|
||||
});
|
||||
}
|
||||
|
||||
return workspaceMigrations;
|
||||
}
|
||||
|
||||
private async createFieldRelationMigration(
|
||||
originalObjectMetadataMap: Record<string, ObjectMetadataEntity>,
|
||||
fieldRelationMetadataCollection: FieldMetadataEntity<FieldMetadataType.RELATION>[],
|
||||
): Promise<Partial<WorkspaceMigrationEntity>[]> {
|
||||
const workspaceMigrations: Partial<WorkspaceMigrationEntity>[] = [];
|
||||
|
||||
for (const sourceFieldMetadata of fieldRelationMetadataCollection) {
|
||||
const sourceObjectMetadata =
|
||||
originalObjectMetadataMap[sourceFieldMetadata.objectMetadataId];
|
||||
const targetObjectMetadata =
|
||||
originalObjectMetadataMap[
|
||||
sourceFieldMetadata.relationTargetObjectMetadataId
|
||||
];
|
||||
|
||||
if (!sourceFieldMetadata.settings) {
|
||||
throw new Error(
|
||||
`FieldMetadata for relation with id ${sourceFieldMetadata.id} has no settings`,
|
||||
);
|
||||
}
|
||||
|
||||
// We're creating it from `ONE_TO_MANY` with the join column so we don't need to create a migration for `MANY_TO_ONE`
|
||||
if (
|
||||
sourceFieldMetadata.settings.relationType === RelationType.MANY_TO_ONE
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!sourceObjectMetadata) {
|
||||
throw new Error(
|
||||
`ObjectMetadata with id ${sourceFieldMetadata.objectMetadataId} not found`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!targetObjectMetadata) {
|
||||
throw new Error(
|
||||
`ObjectMetadata with id ${sourceFieldMetadata.relationTargetObjectMetadataId} not found`,
|
||||
);
|
||||
}
|
||||
|
||||
const targetFieldMetadata = targetObjectMetadata.fields.find(
|
||||
(field) =>
|
||||
field.id === sourceFieldMetadata.relationTargetFieldMetadataId,
|
||||
);
|
||||
|
||||
if (!targetFieldMetadata) {
|
||||
throw new Error(
|
||||
`FieldMetadata with id ${sourceFieldMetadata.relationTargetFieldMetadataId} not found`,
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
!isFieldMetadataEntityOfType(
|
||||
targetFieldMetadata,
|
||||
FieldMetadataType.RELATION,
|
||||
)
|
||||
) {
|
||||
throw new Error(
|
||||
`FieldMetadata with id ${sourceFieldMetadata.relationTargetFieldMetadataId} is not a relation`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!targetFieldMetadata.settings) {
|
||||
throw new Error(
|
||||
`FieldMetadata for relation with id ${sourceFieldMetadata.id} has no settings`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!targetFieldMetadata.settings.joinColumnName) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const migrations: WorkspaceMigrationTableAction[] = [
|
||||
{
|
||||
name: computeObjectTargetTable(targetObjectMetadata),
|
||||
action: WorkspaceMigrationTableActionType.ALTER,
|
||||
columns: [
|
||||
...this.workspaceMigrationFactory.createColumnActions(
|
||||
WorkspaceMigrationColumnActionType.CREATE,
|
||||
targetFieldMetadata,
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: computeObjectTargetTable(targetObjectMetadata),
|
||||
action: WorkspaceMigrationTableActionType.ALTER,
|
||||
columns: [
|
||||
{
|
||||
action: WorkspaceMigrationColumnActionType.CREATE_FOREIGN_KEY,
|
||||
columnName:
|
||||
targetFieldMetadata.settings.joinColumnName ??
|
||||
`${camelCase(targetFieldMetadata.name)}Id`,
|
||||
referencedTableName:
|
||||
computeObjectTargetTable(sourceObjectMetadata),
|
||||
referencedTableColumnName: 'id',
|
||||
isUnique:
|
||||
targetFieldMetadata.settings.relationType ===
|
||||
RelationType.ONE_TO_ONE,
|
||||
onDelete: targetFieldMetadata.settings.onDelete,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
workspaceMigrations.push({
|
||||
workspaceId: sourceFieldMetadata.workspaceId,
|
||||
name: generateMigrationName(
|
||||
`create-relation-from-${sourceObjectMetadata.nameSingular}-to-${targetObjectMetadata.nameSingular}`,
|
||||
),
|
||||
isCustom: false,
|
||||
migrations,
|
||||
});
|
||||
}
|
||||
|
||||
return workspaceMigrations;
|
||||
}
|
||||
|
||||
private async deleteFieldRelationMigration(
|
||||
originalObjectMetadataMap: Record<string, ObjectMetadataEntity>,
|
||||
fieldRelationMetadataCollection: FieldMetadataEntity<FieldMetadataType.RELATION>[],
|
||||
): Promise<Partial<WorkspaceMigrationEntity>[]> {
|
||||
const workspaceMigrations: Partial<WorkspaceMigrationEntity>[] = [];
|
||||
|
||||
for (const sourceFieldMetadata of fieldRelationMetadataCollection) {
|
||||
const sourceObjectMetadata =
|
||||
originalObjectMetadataMap[sourceFieldMetadata.objectMetadataId];
|
||||
const targetObjectMetadata =
|
||||
originalObjectMetadataMap[
|
||||
sourceFieldMetadata.relationTargetObjectMetadataId
|
||||
];
|
||||
|
||||
if (!sourceObjectMetadata) {
|
||||
throw new Error(
|
||||
`ObjectMetadata with id ${sourceFieldMetadata.objectMetadataId} not found`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!targetObjectMetadata) {
|
||||
throw new Error(
|
||||
`ObjectMetadata with id ${sourceFieldMetadata.relationTargetObjectMetadataId} not found`,
|
||||
);
|
||||
}
|
||||
|
||||
const targetFieldMetadata = targetObjectMetadata.fields.find(
|
||||
(field) =>
|
||||
field.id === sourceFieldMetadata.relationTargetFieldMetadataId,
|
||||
);
|
||||
|
||||
if (!targetFieldMetadata) {
|
||||
throw new Error(
|
||||
`FieldMetadata with id ${sourceFieldMetadata.relationTargetFieldMetadataId} not found`,
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
!isFieldMetadataEntityOfType(
|
||||
targetFieldMetadata,
|
||||
FieldMetadataType.RELATION,
|
||||
)
|
||||
) {
|
||||
throw new Error(
|
||||
`FieldMetadata with id ${sourceFieldMetadata.relationTargetFieldMetadataId} is not a relation`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!targetFieldMetadata.settings) {
|
||||
throw new Error(
|
||||
`FieldMetadata for relation with id ${sourceFieldMetadata.id} has no settings`,
|
||||
);
|
||||
}
|
||||
|
||||
const migrations: WorkspaceMigrationTableAction[] = [
|
||||
{
|
||||
name: computeObjectTargetTable(targetObjectMetadata),
|
||||
action: WorkspaceMigrationTableActionType.ALTER,
|
||||
columns: [
|
||||
{
|
||||
action: WorkspaceMigrationColumnActionType.DROP_FOREIGN_KEY,
|
||||
columnName: `${camelCase(targetFieldMetadata.name)}Id`,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: computeObjectTargetTable(targetObjectMetadata),
|
||||
action: WorkspaceMigrationTableActionType.ALTER,
|
||||
columns: [
|
||||
{
|
||||
action: WorkspaceMigrationColumnActionType.DROP,
|
||||
columnName:
|
||||
targetFieldMetadata.settings.joinColumnName ??
|
||||
`${camelCase(targetFieldMetadata.name)}Id`,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
workspaceMigrations.push({
|
||||
workspaceId: sourceFieldMetadata.workspaceId,
|
||||
name: generateMigrationName(
|
||||
`update-relation-from-${sourceObjectMetadata.nameSingular}-to-${targetObjectMetadata.nameSingular}`,
|
||||
),
|
||||
isCustom: false,
|
||||
migrations,
|
||||
});
|
||||
}
|
||||
|
||||
return workspaceMigrations;
|
||||
}
|
||||
}
|
||||
@ -5,6 +5,8 @@ import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { WorkspaceMigrationBuilderAction } from 'src/engine/workspace-manager/workspace-migration-builder/interfaces/workspace-migration-builder-action.interface';
|
||||
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
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 { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util';
|
||||
@ -18,15 +20,18 @@ import {
|
||||
import { WorkspaceMigrationFactory } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.factory';
|
||||
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
|
||||
|
||||
export interface FieldMetadataUpdate {
|
||||
current: FieldMetadataEntity;
|
||||
altered: FieldMetadataEntity;
|
||||
export interface FieldMetadataUpdate<
|
||||
Type extends FieldMetadataType = FieldMetadataType,
|
||||
> {
|
||||
current: FieldMetadataEntity<Type>;
|
||||
altered: FieldMetadataEntity<Type>;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceMigrationFieldFactory {
|
||||
constructor(
|
||||
private readonly workspaceMigrationFactory: WorkspaceMigrationFactory,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
) {}
|
||||
|
||||
async create(
|
||||
@ -86,6 +91,17 @@ export class WorkspaceMigrationFieldFactory {
|
||||
): Promise<Partial<WorkspaceMigrationEntity>[]> {
|
||||
const workspaceMigrations: Partial<WorkspaceMigrationEntity>[] = [];
|
||||
|
||||
if (fieldMetadataCollection.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const workspaceId = fieldMetadataCollection[0]?.workspaceId;
|
||||
|
||||
const isNewRelationEnabled = await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IsNewRelationEnabled,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
const fieldMetadataCollectionGroupByObjectMetadataId =
|
||||
fieldMetadataCollection.reduce(
|
||||
(result, currentFieldMetadata) => {
|
||||
@ -110,7 +126,10 @@ export class WorkspaceMigrationFieldFactory {
|
||||
|
||||
for (const fieldMetadata of fieldMetadataCollection) {
|
||||
// Relations are handled in workspace-migration-relation.factory.ts
|
||||
if (fieldMetadata.type === FieldMetadataType.RELATION) {
|
||||
if (
|
||||
!isNewRelationEnabled &&
|
||||
fieldMetadata.type === FieldMetadataType.RELATION
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -127,7 +146,7 @@ export class WorkspaceMigrationFieldFactory {
|
||||
name: generateMigrationName(
|
||||
`create-${objectMetadata.nameSingular}-fields`,
|
||||
),
|
||||
isCustom: false,
|
||||
isCustom: objectMetadata.isCustom,
|
||||
migrations: [
|
||||
{
|
||||
name: computeObjectTargetTable(
|
||||
@ -149,9 +168,23 @@ export class WorkspaceMigrationFieldFactory {
|
||||
): Promise<Partial<WorkspaceMigrationEntity>[]> {
|
||||
const workspaceMigrations: Partial<WorkspaceMigrationEntity>[] = [];
|
||||
|
||||
if (fieldMetadataUpdateCollection.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const workspaceId = fieldMetadataUpdateCollection[0]?.current.workspaceId;
|
||||
|
||||
const isNewRelationEnabled = await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IsNewRelationEnabled,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
for (const fieldMetadataUpdate of fieldMetadataUpdateCollection) {
|
||||
// Skip relations, because they're just representation and not real columns
|
||||
if (fieldMetadataUpdate.altered.type === FieldMetadataType.RELATION) {
|
||||
if (
|
||||
!isNewRelationEnabled &&
|
||||
fieldMetadataUpdate.altered.type === FieldMetadataType.RELATION
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -211,7 +244,7 @@ export class WorkspaceMigrationFieldFactory {
|
||||
name: generateMigrationName(
|
||||
`update-${fieldMetadataUpdate.altered.name}`,
|
||||
),
|
||||
isCustom: false,
|
||||
isCustom: fieldMetadataUpdate.altered.isCustom,
|
||||
migrations,
|
||||
});
|
||||
}
|
||||
@ -250,7 +283,7 @@ export class WorkspaceMigrationFieldFactory {
|
||||
workspaceMigrations.push({
|
||||
workspaceId: fieldMetadata.workspaceId,
|
||||
name: generateMigrationName(`delete-${fieldMetadata.name}`),
|
||||
isCustom: false,
|
||||
isCustom: fieldMetadata.isCustom,
|
||||
migrations,
|
||||
});
|
||||
}
|
||||
|
||||
@ -109,7 +109,7 @@ export class WorkspaceMigrationObjectFactory {
|
||||
workspaceMigrations.push({
|
||||
workspaceId: objectMetadata.workspaceId,
|
||||
name: generateMigrationName(`create-${objectMetadata.nameSingular}`),
|
||||
isCustom: false,
|
||||
isCustom: objectMetadata.isCustom,
|
||||
migrations,
|
||||
});
|
||||
}
|
||||
@ -136,7 +136,7 @@ export class WorkspaceMigrationObjectFactory {
|
||||
name: generateMigrationName(
|
||||
`rename-${objectMetadataUpdate.current.nameSingular}`,
|
||||
),
|
||||
isCustom: false,
|
||||
isCustom: objectMetadataUpdate.altered.isCustom,
|
||||
migrations: [
|
||||
{
|
||||
name: oldTableName,
|
||||
@ -167,7 +167,7 @@ export class WorkspaceMigrationObjectFactory {
|
||||
workspaceMigrations.push({
|
||||
workspaceId: objectMetadata.workspaceId,
|
||||
name: generateMigrationName(`delete-${objectMetadata.nameSingular}`),
|
||||
isCustom: false,
|
||||
isCustom: objectMetadata.isCustom,
|
||||
migrations: [
|
||||
...(relationMetadataCollection ?? []).map(
|
||||
(relationMetadata) =>
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module';
|
||||
|
||||
import { workspaceMigrationBuilderFactories } from './factories';
|
||||
|
||||
@Module({
|
||||
imports: [WorkspaceMigrationModule],
|
||||
imports: [WorkspaceMigrationModule, FeatureFlagModule],
|
||||
providers: [...workspaceMigrationBuilderFactories],
|
||||
exports: [...workspaceMigrationBuilderFactories],
|
||||
})
|
||||
|
||||
@ -3,6 +3,10 @@ import { RelationOnDeleteAction } from 'src/engine/metadata-modules/relation-met
|
||||
export const convertOnDeleteActionToOnDelete = (
|
||||
onDeleteAction: RelationOnDeleteAction | undefined,
|
||||
): 'CASCADE' | 'SET NULL' | 'RESTRICT' | 'NO ACTION' | undefined => {
|
||||
if (!onDeleteAction) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
switch (onDeleteAction) {
|
||||
case 'CASCADE':
|
||||
return 'CASCADE';
|
||||
|
||||
@ -7,6 +7,7 @@ import {
|
||||
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
|
||||
RunOnWorkspaceArgs,
|
||||
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
@ -25,6 +26,7 @@ export class SyncWorkspaceMetadataCommand extends ActiveOrSuspendedWorkspacesMig
|
||||
private readonly workspaceSyncMetadataService: WorkspaceSyncMetadataService,
|
||||
private readonly dataSourceService: DataSourceService,
|
||||
private readonly syncWorkspaceLoggerService: SyncWorkspaceLoggerService,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager);
|
||||
@ -45,11 +47,15 @@ export class SyncWorkspaceMetadataCommand extends ActiveOrSuspendedWorkspacesMig
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
const featureFlags =
|
||||
await this.featureFlagService.getWorkspaceFeatureFlagsMap(workspaceId);
|
||||
|
||||
const { storage, workspaceMigrations } =
|
||||
await this.workspaceSyncMetadataService.synchronize(
|
||||
{
|
||||
workspaceId,
|
||||
dataSourceId: dataSourceMetadata.id,
|
||||
featureFlags,
|
||||
},
|
||||
{ applyChanges: !options.dryRun },
|
||||
);
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { WorkspaceModule } from 'src/engine/core-modules/workspace/workspace.module';
|
||||
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
||||
@ -19,6 +20,7 @@ import { SyncWorkspaceMetadataCommand } from './sync-workspace-metadata.command'
|
||||
WorkspaceModule,
|
||||
DataSourceModule,
|
||||
WorkspaceDataSourceModule,
|
||||
FeatureFlagModule,
|
||||
TypeOrmModule.forFeature([Workspace], 'core'),
|
||||
SyncWorkspaceLoggerModule,
|
||||
],
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { WorkspaceFieldRelationComparator } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/workspace-field-relation.comparator';
|
||||
import { WorkspaceIndexComparator } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/workspace-index.comparator';
|
||||
|
||||
import { WorkspaceFieldComparator } from './workspace-field.comparator';
|
||||
@ -6,6 +7,7 @@ import { WorkspaceRelationComparator } from './workspace-relation.comparator';
|
||||
|
||||
export const workspaceSyncMetadataComparators = [
|
||||
WorkspaceFieldComparator,
|
||||
WorkspaceFieldRelationComparator,
|
||||
WorkspaceObjectComparator,
|
||||
WorkspaceRelationComparator,
|
||||
WorkspaceIndexComparator,
|
||||
|
||||
@ -0,0 +1,250 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import diff, { DifferenceChange, DifferenceRemove } from 'microdiff';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { FieldMetadataRelationSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
|
||||
import {
|
||||
ComparatorAction,
|
||||
FieldRelationComparatorResult,
|
||||
} from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/comparator.interface';
|
||||
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { transformMetadataForComparison } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/utils/transform-metadata-for-comparison.util';
|
||||
|
||||
const fieldPropertiesToCompare = [
|
||||
'settings',
|
||||
'relationTargetObjectMetadataId',
|
||||
'relationTargetFieldMetadataId',
|
||||
];
|
||||
|
||||
const fieldPropertiesToStringify = ['settings'] as const;
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceFieldRelationComparator {
|
||||
constructor() {}
|
||||
|
||||
public compare(
|
||||
originalFieldMetadataCollection: FieldMetadataEntity<FieldMetadataType.RELATION>[],
|
||||
standardFieldMetadataCollection: FieldMetadataEntity<FieldMetadataType.RELATION>[],
|
||||
): FieldRelationComparatorResult[] {
|
||||
const result: FieldRelationComparatorResult[] = [];
|
||||
const propertiesMap: Record<
|
||||
string,
|
||||
Partial<FieldMetadataEntity<FieldMetadataType.RELATION>>
|
||||
> = {};
|
||||
|
||||
// Double security to only compare non-custom fields
|
||||
const filteredOriginalFieldCollection =
|
||||
originalFieldMetadataCollection.filter((field) => !field.isCustom);
|
||||
const originalFieldMetadataMap = transformMetadataForComparison(
|
||||
filteredOriginalFieldCollection,
|
||||
{
|
||||
shouldIgnoreProperty: (property) => {
|
||||
if (fieldPropertiesToCompare.includes(property)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
propertiesToStringify: fieldPropertiesToStringify,
|
||||
keyFactory(datum) {
|
||||
// Happen when the field is custom
|
||||
return datum.standardId || datum.name;
|
||||
},
|
||||
},
|
||||
);
|
||||
const standardFieldMetadataMap = transformMetadataForComparison(
|
||||
standardFieldMetadataCollection,
|
||||
{
|
||||
shouldIgnoreProperty: (property) => {
|
||||
if (fieldPropertiesToCompare.includes(property)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
propertiesToStringify: fieldPropertiesToStringify,
|
||||
keyFactory(datum) {
|
||||
// Happen when the field is custom
|
||||
return datum.standardId || datum.name;
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
// Compare fields
|
||||
const fieldMetadataDifference = diff(
|
||||
originalFieldMetadataMap,
|
||||
standardFieldMetadataMap,
|
||||
);
|
||||
|
||||
const fieldMetadataDifferenceMap = fieldMetadataDifference.reduce(
|
||||
(acc, difference) => {
|
||||
const fieldId = difference.path[0];
|
||||
|
||||
if (difference.type === 'CREATE') {
|
||||
throw new Error(
|
||||
`CREATE should never happen when comparing relations as they are created previously`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!acc[fieldId]) {
|
||||
acc[fieldId] = [];
|
||||
}
|
||||
|
||||
acc[fieldId].push(difference);
|
||||
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, (DifferenceChange | DifferenceRemove)[]>,
|
||||
);
|
||||
|
||||
differenceLoop: for (const [fieldId, differences] of Object.entries(
|
||||
fieldMetadataDifferenceMap,
|
||||
)) {
|
||||
const findField = (
|
||||
field: FieldMetadataEntity<FieldMetadataType.RELATION>,
|
||||
) => {
|
||||
return field.standardId === fieldId;
|
||||
};
|
||||
// Object shouldn't have thousands of fields, so we can use find here
|
||||
const standardFieldMetadata =
|
||||
standardFieldMetadataCollection.find(findField);
|
||||
const originalFieldMetadata =
|
||||
originalFieldMetadataCollection.find(findField);
|
||||
const allNewPropertiesAreNull = Object.values(differences).every(
|
||||
(difference) => {
|
||||
if (difference.type === 'REMOVE') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return difference.value === null;
|
||||
},
|
||||
);
|
||||
const allOldPropertiesAreNull = Object.values(differences).every(
|
||||
(difference) => difference.oldValue === null,
|
||||
);
|
||||
let relationTypeChange = false;
|
||||
|
||||
if (!originalFieldMetadata) {
|
||||
throw new Error(`Field ${fieldId} not found in originalObjectMetadata`);
|
||||
}
|
||||
|
||||
if (!standardFieldMetadata) {
|
||||
throw new Error(`Field ${fieldId} not found in standardObjectMetadata`);
|
||||
}
|
||||
|
||||
for (const difference of differences) {
|
||||
const property = difference.path[difference.path.length - 1];
|
||||
|
||||
if (difference.type === 'REMOVE') {
|
||||
// Whole relation is removed
|
||||
if (property === fieldId) {
|
||||
result.push({
|
||||
action: ComparatorAction.DELETE,
|
||||
object: originalFieldMetadata,
|
||||
});
|
||||
|
||||
continue differenceLoop;
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`REMOVE partial part of relation should never happen, it should ben an update`,
|
||||
);
|
||||
}
|
||||
|
||||
// If the old value and the new value are both null, skip
|
||||
// Database is storing null, and we can get undefined here
|
||||
if (
|
||||
difference.oldValue === null &&
|
||||
(difference.value === null || difference.value === undefined)
|
||||
) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (typeof property !== 'string') {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!propertiesMap[fieldId]) {
|
||||
propertiesMap[fieldId] = {};
|
||||
}
|
||||
|
||||
// If the property is a stringified JSON, parse it
|
||||
if (
|
||||
(fieldPropertiesToStringify as readonly string[]).includes(property)
|
||||
) {
|
||||
const newValue = this.parseJSONOrString(difference.value);
|
||||
|
||||
if (property === 'settings' && difference.oldValue) {
|
||||
const newSettings = newValue as FieldMetadataRelationSettings;
|
||||
const oldSettings =
|
||||
difference.oldValue as FieldMetadataRelationSettings;
|
||||
|
||||
// Check if the relation type has changed
|
||||
if (oldSettings.relationType !== newSettings.relationType) {
|
||||
relationTypeChange = true;
|
||||
}
|
||||
}
|
||||
|
||||
propertiesMap[fieldId][property] = newValue;
|
||||
} else {
|
||||
propertiesMap[fieldId][property] = difference.value;
|
||||
}
|
||||
}
|
||||
|
||||
if (relationTypeChange) {
|
||||
result.push({
|
||||
action: ComparatorAction.DELETE,
|
||||
object: originalFieldMetadata,
|
||||
});
|
||||
|
||||
result.push({
|
||||
action: ComparatorAction.CREATE,
|
||||
object: {
|
||||
...propertiesMap[fieldId],
|
||||
id: originalFieldMetadata.id,
|
||||
standardId: standardFieldMetadata.standardId ?? undefined,
|
||||
},
|
||||
});
|
||||
} else if (allOldPropertiesAreNull) {
|
||||
result.push({
|
||||
action: ComparatorAction.CREATE,
|
||||
object: {
|
||||
...propertiesMap[fieldId],
|
||||
id: originalFieldMetadata.id,
|
||||
standardId: standardFieldMetadata.standardId ?? undefined,
|
||||
},
|
||||
});
|
||||
} else if (allNewPropertiesAreNull) {
|
||||
result.push({
|
||||
action: ComparatorAction.DELETE,
|
||||
object: originalFieldMetadata,
|
||||
});
|
||||
} else {
|
||||
result.push({
|
||||
action: ComparatorAction.UPDATE,
|
||||
object: {
|
||||
...propertiesMap[fieldId],
|
||||
id: originalFieldMetadata.id,
|
||||
standardId: standardFieldMetadata.standardId ?? undefined,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private parseJSONOrString(value: string | null): string | object | null {
|
||||
if (value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
} catch {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
import { StandardIndexFactory } from 'src/engine/workspace-manager/workspace-sync-metadata/factories/standard-index.factory';
|
||||
|
||||
import { StandardFieldRelationFactory } from './standard-field-relation.factory';
|
||||
import { StandardFieldFactory } from './standard-field.factory';
|
||||
import { StandardObjectFactory } from './standard-object.factory';
|
||||
import { StandardRelationFactory } from './standard-relation.factory';
|
||||
@ -8,5 +9,6 @@ export const workspaceSyncMetadataFactories = [
|
||||
StandardFieldFactory,
|
||||
StandardObjectFactory,
|
||||
StandardRelationFactory,
|
||||
StandardFieldRelationFactory,
|
||||
StandardIndexFactory,
|
||||
];
|
||||
|
||||
@ -0,0 +1,186 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { WorkspaceSyncContext } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/workspace-sync-context.interface';
|
||||
|
||||
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 { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
|
||||
import { metadataArgsStorage } from 'src/engine/twenty-orm/storage/metadata-args.storage';
|
||||
import { getJoinColumn } from 'src/engine/twenty-orm/utils/get-join-column.util';
|
||||
import { convertClassNameToObjectMetadataName } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/convert-class-to-object-metadata-name.util';
|
||||
import { isGatedAndNotEnabled } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/is-gate-and-not-enabled.util';
|
||||
import { assert } from 'src/utils/assert';
|
||||
|
||||
interface CustomRelationFactory {
|
||||
object: ObjectMetadataEntity;
|
||||
metadata: typeof BaseWorkspaceEntity;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class StandardFieldRelationFactory {
|
||||
createFieldRelationForCustomObject(
|
||||
customObjectFactories: CustomRelationFactory[],
|
||||
context: WorkspaceSyncContext,
|
||||
originalObjectMetadataMap: Record<string, ObjectMetadataEntity>,
|
||||
): FieldMetadataEntity<FieldMetadataType.RELATION>[] {
|
||||
return customObjectFactories.flatMap((customObjectFactory) =>
|
||||
this.updateFieldRelationMetadata(
|
||||
customObjectFactory,
|
||||
context,
|
||||
originalObjectMetadataMap,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
createFieldRelationForStandardObject(
|
||||
standardObjectMetadataDefinitions: (typeof BaseWorkspaceEntity)[],
|
||||
context: WorkspaceSyncContext,
|
||||
originalObjectMetadataMap: Record<string, ObjectMetadataEntity>,
|
||||
): Map<string, FieldMetadataEntity<FieldMetadataType.RELATION>[]> {
|
||||
return standardObjectMetadataDefinitions.reduce(
|
||||
(acc, standardObjectMetadata) => {
|
||||
const workspaceEntityMetadataArgs = metadataArgsStorage.filterEntities(
|
||||
standardObjectMetadata,
|
||||
);
|
||||
|
||||
if (!workspaceEntityMetadataArgs) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
if (
|
||||
isGatedAndNotEnabled(
|
||||
workspaceEntityMetadataArgs.gate,
|
||||
context.featureFlags,
|
||||
)
|
||||
) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
acc.set(
|
||||
workspaceEntityMetadataArgs.standardId,
|
||||
this.updateFieldRelationMetadata(
|
||||
standardObjectMetadata,
|
||||
context,
|
||||
originalObjectMetadataMap,
|
||||
),
|
||||
);
|
||||
|
||||
return acc;
|
||||
},
|
||||
new Map<string, FieldMetadataEntity<FieldMetadataType.RELATION>[]>(),
|
||||
);
|
||||
}
|
||||
|
||||
private updateFieldRelationMetadata(
|
||||
workspaceEntityOrCustomRelationFactory:
|
||||
| typeof BaseWorkspaceEntity
|
||||
| CustomRelationFactory,
|
||||
context: WorkspaceSyncContext,
|
||||
originalObjectMetadataMap: Record<string, ObjectMetadataEntity>,
|
||||
): FieldMetadataEntity<FieldMetadataType.RELATION>[] {
|
||||
const target =
|
||||
'metadata' in workspaceEntityOrCustomRelationFactory
|
||||
? workspaceEntityOrCustomRelationFactory.metadata
|
||||
: workspaceEntityOrCustomRelationFactory;
|
||||
const workspaceEntity =
|
||||
'metadata' in workspaceEntityOrCustomRelationFactory
|
||||
? metadataArgsStorage.filterExtendedEntities(target)
|
||||
: metadataArgsStorage.filterEntities(target);
|
||||
const workspaceRelationMetadataArgsCollection =
|
||||
metadataArgsStorage.filterRelations(target);
|
||||
|
||||
if (!workspaceEntity) {
|
||||
throw new Error(
|
||||
`Object metadata decorator not found, can't parse ${target.name}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
!workspaceRelationMetadataArgsCollection ||
|
||||
isGatedAndNotEnabled(workspaceEntity?.gate, context.featureFlags)
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return workspaceRelationMetadataArgsCollection
|
||||
.filter(
|
||||
(workspaceRelationMetadataArgs) =>
|
||||
!isGatedAndNotEnabled(
|
||||
workspaceRelationMetadataArgs.gate,
|
||||
context.featureFlags,
|
||||
),
|
||||
)
|
||||
.map((workspaceRelationMetadataArgs) => {
|
||||
// Compute reflect relation metadata
|
||||
const sourceObjectNameSingular =
|
||||
'object' in workspaceEntityOrCustomRelationFactory
|
||||
? workspaceEntityOrCustomRelationFactory.object.nameSingular
|
||||
: convertClassNameToObjectMetadataName(
|
||||
workspaceRelationMetadataArgs.target.name,
|
||||
);
|
||||
const inverseSideTarget =
|
||||
workspaceRelationMetadataArgs.inverseSideTarget();
|
||||
const targetObjectNameSingular = convertClassNameToObjectMetadataName(
|
||||
inverseSideTarget.name,
|
||||
);
|
||||
const sourceFieldMetadataName = workspaceRelationMetadataArgs.name;
|
||||
const targetFieldMetadataName =
|
||||
(workspaceRelationMetadataArgs.inverseSideFieldKey as
|
||||
| string
|
||||
| undefined) ?? sourceObjectNameSingular;
|
||||
const sourceObjectMetadata =
|
||||
originalObjectMetadataMap[sourceObjectNameSingular];
|
||||
const joinColumnsMetadataArgsCollection =
|
||||
metadataArgsStorage.filterJoinColumns(target);
|
||||
const joinColumnName = getJoinColumn(
|
||||
joinColumnsMetadataArgsCollection,
|
||||
workspaceRelationMetadataArgs,
|
||||
);
|
||||
|
||||
assert(
|
||||
sourceObjectMetadata,
|
||||
`Source object ${sourceObjectNameSingular} not found in databse for relation ${workspaceRelationMetadataArgs.name} of type ${workspaceRelationMetadataArgs.type}`,
|
||||
);
|
||||
|
||||
const targetObjectMetadata =
|
||||
originalObjectMetadataMap[targetObjectNameSingular];
|
||||
|
||||
assert(
|
||||
targetObjectMetadata,
|
||||
`Target object ${targetObjectNameSingular} not found in databse for relation ${workspaceRelationMetadataArgs.name} of type ${workspaceRelationMetadataArgs.type}`,
|
||||
);
|
||||
|
||||
const sourceFieldMetadata = sourceObjectMetadata?.fields.find(
|
||||
(field) => field.name === sourceFieldMetadataName,
|
||||
) as FieldMetadataEntity<FieldMetadataType.RELATION>;
|
||||
|
||||
assert(
|
||||
sourceFieldMetadata,
|
||||
`Source field ${sourceFieldMetadataName} not found in object ${sourceObjectNameSingular} for relation ${workspaceRelationMetadataArgs.name} of type ${workspaceRelationMetadataArgs.type}`,
|
||||
);
|
||||
|
||||
const targetFieldMetadata = targetObjectMetadata?.fields.find(
|
||||
(field) => field.name === targetFieldMetadataName,
|
||||
) as FieldMetadataEntity<FieldMetadataType.RELATION>;
|
||||
|
||||
assert(
|
||||
targetFieldMetadata,
|
||||
`Target field ${targetFieldMetadataName} not found in object ${targetObjectNameSingular} for relation ${workspaceRelationMetadataArgs.name} of type ${workspaceRelationMetadataArgs.type}`,
|
||||
);
|
||||
|
||||
return {
|
||||
...sourceFieldMetadata,
|
||||
type: FieldMetadataType.RELATION,
|
||||
settings: {
|
||||
relationType: workspaceRelationMetadataArgs.type,
|
||||
onDelete: workspaceRelationMetadataArgs.onDelete,
|
||||
joinColumnName,
|
||||
},
|
||||
relationTargetObjectMetadataId: targetObjectMetadata.id,
|
||||
relationTargetFieldMetadataId: targetFieldMetadata.id,
|
||||
} satisfies FieldMetadataEntity<FieldMetadataType.RELATION>;
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
import { WorkspaceDynamicRelationMetadataArgs } from 'src/engine/twenty-orm/interfaces/workspace-dynamic-relation-metadata-args.interface';
|
||||
import { WorkspaceEntityMetadataArgs } from 'src/engine/twenty-orm/interfaces/workspace-entity-metadata-args.interface';
|
||||
import { WorkspaceFieldMetadataArgs } from 'src/engine/twenty-orm/interfaces/workspace-field-metadata-args.interface';
|
||||
@ -13,7 +13,7 @@ import {
|
||||
} from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-field-metadata.interface';
|
||||
import { WorkspaceSyncContext } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/workspace-sync-context.interface';
|
||||
|
||||
import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
|
||||
import { metadataArgsStorage } from 'src/engine/twenty-orm/storage/metadata-args.storage';
|
||||
import { getJoinColumn } from 'src/engine/twenty-orm/utils/get-join-column.util';
|
||||
@ -25,13 +25,11 @@ export class StandardFieldFactory {
|
||||
create(
|
||||
target: typeof BaseWorkspaceEntity,
|
||||
context: WorkspaceSyncContext,
|
||||
workspaceFeatureFlagsMap: FeatureFlagMap,
|
||||
): (PartialFieldMetadata | PartialComputedFieldMetadata)[];
|
||||
|
||||
create(
|
||||
targets: (typeof BaseWorkspaceEntity)[],
|
||||
context: WorkspaceSyncContext,
|
||||
workspaceFeatureFlagsMap: FeatureFlagMap, // Map of standardId to field metadata
|
||||
): Map<string, (PartialFieldMetadata | PartialComputedFieldMetadata)[]>;
|
||||
|
||||
create(
|
||||
@ -39,7 +37,6 @@ export class StandardFieldFactory {
|
||||
| typeof BaseWorkspaceEntity
|
||||
| (typeof BaseWorkspaceEntity)[],
|
||||
context: WorkspaceSyncContext,
|
||||
workspaceFeatureFlagsMap: FeatureFlagMap,
|
||||
):
|
||||
| (PartialFieldMetadata | PartialComputedFieldMetadata)[]
|
||||
| Map<string, (PartialFieldMetadata | PartialComputedFieldMetadata)[]> {
|
||||
@ -55,7 +52,7 @@ export class StandardFieldFactory {
|
||||
if (
|
||||
isGatedAndNotEnabled(
|
||||
workspaceEntityMetadataArgs.gate,
|
||||
workspaceFeatureFlagsMap,
|
||||
context.featureFlags,
|
||||
)
|
||||
) {
|
||||
return acc;
|
||||
@ -63,7 +60,7 @@ export class StandardFieldFactory {
|
||||
|
||||
acc.set(
|
||||
workspaceEntityMetadataArgs.standardId,
|
||||
this.create(target, context, workspaceFeatureFlagsMap),
|
||||
this.create(target, context),
|
||||
);
|
||||
|
||||
return acc;
|
||||
@ -79,21 +76,18 @@ export class StandardFieldFactory {
|
||||
workspaceEntityMetadataArgs,
|
||||
metadataCollections.fields,
|
||||
context,
|
||||
workspaceFeatureFlagsMap,
|
||||
this.createFieldMetadata,
|
||||
),
|
||||
...this.processMetadata(
|
||||
workspaceEntityMetadataArgs,
|
||||
metadataCollections.relations,
|
||||
context,
|
||||
workspaceFeatureFlagsMap,
|
||||
this.createFieldRelationMetadata,
|
||||
),
|
||||
...this.processMetadata(
|
||||
workspaceEntityMetadataArgs,
|
||||
metadataCollections.dynamicRelations,
|
||||
context,
|
||||
workspaceFeatureFlagsMap,
|
||||
this.createComputedFieldRelationMetadata,
|
||||
),
|
||||
];
|
||||
@ -114,22 +108,15 @@ export class StandardFieldFactory {
|
||||
workspaceEntityMetadataArgs: WorkspaceEntityMetadataArgs | undefined,
|
||||
metadataArgs: T[],
|
||||
context: WorkspaceSyncContext,
|
||||
featureFlagsMap: FeatureFlagMap,
|
||||
createMetadata: (
|
||||
workspaceEntityMetadataArgs: WorkspaceEntityMetadataArgs | undefined,
|
||||
args: T,
|
||||
context: WorkspaceSyncContext,
|
||||
featureFlagsMap: FeatureFlagMap,
|
||||
) => U[],
|
||||
): U[] {
|
||||
return metadataArgs
|
||||
.flatMap((args) =>
|
||||
createMetadata(
|
||||
workspaceEntityMetadataArgs,
|
||||
args,
|
||||
context,
|
||||
featureFlagsMap,
|
||||
),
|
||||
createMetadata(workspaceEntityMetadataArgs, args, context),
|
||||
)
|
||||
.filter(Boolean) as U[];
|
||||
}
|
||||
@ -141,12 +128,11 @@ export class StandardFieldFactory {
|
||||
workspaceEntityMetadataArgs: WorkspaceEntityMetadataArgs | undefined,
|
||||
workspaceFieldMetadataArgs: WorkspaceFieldMetadataArgs,
|
||||
context: WorkspaceSyncContext,
|
||||
workspaceFeatureFlagsMap: FeatureFlagMap,
|
||||
): PartialFieldMetadata[] {
|
||||
if (
|
||||
isGatedAndNotEnabled(
|
||||
workspaceFieldMetadataArgs.gate,
|
||||
workspaceFeatureFlagsMap,
|
||||
context.featureFlags,
|
||||
)
|
||||
) {
|
||||
return [];
|
||||
@ -182,8 +168,10 @@ export class StandardFieldFactory {
|
||||
workspaceEntityMetadataArgs: WorkspaceEntityMetadataArgs | undefined,
|
||||
workspaceRelationMetadataArgs: WorkspaceRelationMetadataArgs,
|
||||
context: WorkspaceSyncContext,
|
||||
workspaceFeatureFlagsMap: FeatureFlagMap,
|
||||
): PartialFieldMetadata[] {
|
||||
const isNewRelationEnabled =
|
||||
context.featureFlags[FeatureFlagKey.IsNewRelationEnabled];
|
||||
|
||||
const fieldMetadataCollection: PartialFieldMetadata[] = [];
|
||||
const foreignKeyStandardId = createDeterministicUuid(
|
||||
workspaceRelationMetadataArgs.standardId,
|
||||
@ -200,13 +188,14 @@ export class StandardFieldFactory {
|
||||
if (
|
||||
isGatedAndNotEnabled(
|
||||
workspaceRelationMetadataArgs.gate,
|
||||
workspaceFeatureFlagsMap,
|
||||
context.featureFlags,
|
||||
)
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (joinColumn) {
|
||||
// We don't want to create the join column field metadata for new relation
|
||||
if (!isNewRelationEnabled && joinColumn) {
|
||||
fieldMetadataCollection.push({
|
||||
type: FieldMetadataType.UUID,
|
||||
standardId: foreignKeyStandardId,
|
||||
@ -222,8 +211,7 @@ export class StandardFieldFactory {
|
||||
isSystem: true,
|
||||
isNullable: workspaceRelationMetadataArgs.isNullable,
|
||||
isUnique:
|
||||
workspaceRelationMetadataArgs.type ===
|
||||
RelationMetadataType.ONE_TO_ONE,
|
||||
workspaceRelationMetadataArgs.type === RelationType.ONE_TO_ONE,
|
||||
isActive: workspaceRelationMetadataArgs.isActive ?? true,
|
||||
});
|
||||
}
|
||||
@ -235,15 +223,13 @@ export class StandardFieldFactory {
|
||||
label: workspaceRelationMetadataArgs.label,
|
||||
description: workspaceRelationMetadataArgs.description,
|
||||
icon: workspaceRelationMetadataArgs.icon,
|
||||
defaultValue: null,
|
||||
workspaceId: context.workspaceId,
|
||||
isCustom: false,
|
||||
isSystem:
|
||||
workspaceEntityMetadataArgs?.isSystem ||
|
||||
workspaceRelationMetadataArgs.isSystem,
|
||||
isNullable: true,
|
||||
isUnique:
|
||||
workspaceRelationMetadataArgs.type === RelationMetadataType.ONE_TO_ONE,
|
||||
isUnique: workspaceRelationMetadataArgs.type === RelationType.ONE_TO_ONE,
|
||||
isActive: workspaceRelationMetadataArgs.isActive ?? true,
|
||||
});
|
||||
|
||||
@ -259,13 +245,12 @@ export class StandardFieldFactory {
|
||||
| WorkspaceDynamicRelationMetadataArgs
|
||||
| undefined,
|
||||
context: WorkspaceSyncContext,
|
||||
workspaceFeatureFlagsMap: FeatureFlagMap,
|
||||
): PartialComputedFieldMetadata[] {
|
||||
if (
|
||||
!workspaceDynamicRelationMetadataArgs ||
|
||||
isGatedAndNotEnabled(
|
||||
workspaceDynamicRelationMetadataArgs.gate,
|
||||
workspaceFeatureFlagsMap,
|
||||
context.featureFlags,
|
||||
)
|
||||
) {
|
||||
return [];
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
|
||||
import { PartialIndexMetadata } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-index-metadata.interface';
|
||||
import { WorkspaceSyncContext } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/workspace-sync-context.interface';
|
||||
|
||||
@ -20,7 +19,6 @@ export class StandardIndexFactory {
|
||||
context: WorkspaceSyncContext,
|
||||
originalStandardObjectMetadataMap: Record<string, ObjectMetadataEntity>,
|
||||
originalCustomObjectMetadataMap: Record<string, ObjectMetadataEntity>,
|
||||
workspaceFeatureFlagsMap: FeatureFlagMap,
|
||||
): Partial<IndexMetadataEntity>[] {
|
||||
const standardIndexOnStandardObjects =
|
||||
standardObjectMetadataDefinitions.flatMap((standardObjectMetadata) =>
|
||||
@ -28,7 +26,6 @@ export class StandardIndexFactory {
|
||||
standardObjectMetadata,
|
||||
context,
|
||||
originalStandardObjectMetadataMap,
|
||||
workspaceFeatureFlagsMap,
|
||||
),
|
||||
);
|
||||
|
||||
@ -36,7 +33,6 @@ export class StandardIndexFactory {
|
||||
this.createStandardIndexMetadataForCustomObject(
|
||||
context,
|
||||
originalCustomObjectMetadataMap,
|
||||
workspaceFeatureFlagsMap,
|
||||
);
|
||||
|
||||
return [
|
||||
@ -49,7 +45,6 @@ export class StandardIndexFactory {
|
||||
target: typeof BaseWorkspaceEntity,
|
||||
context: WorkspaceSyncContext,
|
||||
originalStandardObjectMetadataMap: Record<string, ObjectMetadataEntity>,
|
||||
workspaceFeatureFlagsMap: FeatureFlagMap,
|
||||
): Partial<IndexMetadataEntity>[] {
|
||||
const workspaceEntity = metadataArgsStorage.filterEntities(target);
|
||||
|
||||
@ -59,7 +54,7 @@ export class StandardIndexFactory {
|
||||
);
|
||||
}
|
||||
|
||||
if (isGatedAndNotEnabled(workspaceEntity?.gate, workspaceFeatureFlagsMap)) {
|
||||
if (isGatedAndNotEnabled(workspaceEntity?.gate, context.featureFlags)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@ -68,7 +63,7 @@ export class StandardIndexFactory {
|
||||
.filter((workspaceIndexMetadataArgs) => {
|
||||
return !isGatedAndNotEnabled(
|
||||
workspaceIndexMetadataArgs.gate,
|
||||
workspaceFeatureFlagsMap,
|
||||
context.featureFlags,
|
||||
);
|
||||
});
|
||||
|
||||
@ -102,7 +97,6 @@ export class StandardIndexFactory {
|
||||
private createStandardIndexMetadataForCustomObject(
|
||||
context: WorkspaceSyncContext,
|
||||
originalCustomObjectMetadataMap: Record<string, ObjectMetadataEntity>,
|
||||
workspaceFeatureFlagsMap: FeatureFlagMap,
|
||||
): Partial<IndexMetadataEntity>[] {
|
||||
const target = CustomWorkspaceEntity;
|
||||
const workspaceEntity = metadataArgsStorage.filterExtendedEntities(target);
|
||||
@ -118,7 +112,7 @@ export class StandardIndexFactory {
|
||||
.filter((workspaceIndexMetadataArgs) => {
|
||||
return !isGatedAndNotEnabled(
|
||||
workspaceIndexMetadataArgs.gate,
|
||||
workspaceFeatureFlagsMap,
|
||||
context.featureFlags,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
|
||||
import { PartialWorkspaceEntity } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-object-metadata.interface';
|
||||
import { WorkspaceSyncContext } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/workspace-sync-context.interface';
|
||||
|
||||
@ -13,19 +12,15 @@ export class StandardObjectFactory {
|
||||
create(
|
||||
standardObjectMetadataDefinitions: (typeof BaseWorkspaceEntity)[],
|
||||
context: WorkspaceSyncContext,
|
||||
workspaceFeatureFlagsMap: FeatureFlagMap,
|
||||
): Omit<PartialWorkspaceEntity, 'fields' | 'indexMetadatas'>[] {
|
||||
return standardObjectMetadataDefinitions
|
||||
.map((metadata) =>
|
||||
this.createObjectMetadata(metadata, context, workspaceFeatureFlagsMap),
|
||||
)
|
||||
.map((metadata) => this.createObjectMetadata(metadata, context))
|
||||
.filter((metadata): metadata is PartialWorkspaceEntity => !!metadata);
|
||||
}
|
||||
|
||||
private createObjectMetadata(
|
||||
target: typeof BaseWorkspaceEntity,
|
||||
context: WorkspaceSyncContext,
|
||||
workspaceFeatureFlagsMap: FeatureFlagMap,
|
||||
): Omit<PartialWorkspaceEntity, 'fields' | 'indexMetadatas'> | undefined {
|
||||
const workspaceEntityMetadataArgs =
|
||||
metadataArgsStorage.filterEntities(target);
|
||||
@ -39,7 +34,7 @@ export class StandardObjectFactory {
|
||||
if (
|
||||
isGatedAndNotEnabled(
|
||||
workspaceEntityMetadataArgs.gate,
|
||||
workspaceFeatureFlagsMap,
|
||||
context.featureFlags,
|
||||
)
|
||||
) {
|
||||
return undefined;
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
import { WorkspaceSyncContext } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/workspace-sync-context.interface';
|
||||
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
|
||||
|
||||
import { isGatedAndNotEnabled } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/is-gate-and-not-enabled.util';
|
||||
import { assert } from 'src/utils/assert';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import {
|
||||
RelationMetadataEntity,
|
||||
RelationMetadataType,
|
||||
} from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { convertClassNameToObjectMetadataName } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/convert-class-to-object-metadata-name.util';
|
||||
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
|
||||
import { metadataArgsStorage } from 'src/engine/twenty-orm/storage/metadata-args.storage';
|
||||
import { convertClassNameToObjectMetadataName } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/convert-class-to-object-metadata-name.util';
|
||||
import { isGatedAndNotEnabled } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/is-gate-and-not-enabled.util';
|
||||
import { assert } from 'src/utils/assert';
|
||||
|
||||
interface CustomRelationFactory {
|
||||
object: ObjectMetadataEntity;
|
||||
@ -25,14 +25,12 @@ export class StandardRelationFactory {
|
||||
customObjectFactories: CustomRelationFactory[],
|
||||
context: WorkspaceSyncContext,
|
||||
originalObjectMetadataMap: Record<string, ObjectMetadataEntity>,
|
||||
workspaceFeatureFlagsMap: FeatureFlagMap,
|
||||
): Partial<RelationMetadataEntity>[];
|
||||
|
||||
create(
|
||||
standardObjectMetadataDefinitions: (typeof BaseWorkspaceEntity)[],
|
||||
context: WorkspaceSyncContext,
|
||||
originalObjectMetadataMap: Record<string, ObjectMetadataEntity>,
|
||||
workspaceFeatureFlagsMap: FeatureFlagMap,
|
||||
): Partial<RelationMetadataEntity>[];
|
||||
|
||||
create(
|
||||
@ -44,7 +42,6 @@ export class StandardRelationFactory {
|
||||
}[],
|
||||
context: WorkspaceSyncContext,
|
||||
originalObjectMetadataMap: Record<string, ObjectMetadataEntity>,
|
||||
workspaceFeatureFlagsMap: FeatureFlagMap,
|
||||
): Partial<RelationMetadataEntity>[] {
|
||||
return standardObjectMetadataDefinitionsOrCustomObjectFactories.flatMap(
|
||||
(
|
||||
@ -56,7 +53,6 @@ export class StandardRelationFactory {
|
||||
standardObjectMetadata,
|
||||
context,
|
||||
originalObjectMetadataMap,
|
||||
workspaceFeatureFlagsMap,
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -67,7 +63,6 @@ export class StandardRelationFactory {
|
||||
| CustomRelationFactory,
|
||||
context: WorkspaceSyncContext,
|
||||
originalObjectMetadataMap: Record<string, ObjectMetadataEntity>,
|
||||
workspaceFeatureFlagsMap: FeatureFlagMap,
|
||||
): Partial<RelationMetadataEntity>[] {
|
||||
const target =
|
||||
'metadata' in workspaceEntityOrCustomRelationFactory
|
||||
@ -88,7 +83,7 @@ export class StandardRelationFactory {
|
||||
|
||||
if (
|
||||
!workspaceRelationMetadataArgsCollection ||
|
||||
isGatedAndNotEnabled(workspaceEntity?.gate, workspaceFeatureFlagsMap)
|
||||
isGatedAndNotEnabled(workspaceEntity?.gate, context.featureFlags)
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
@ -96,16 +91,13 @@ export class StandardRelationFactory {
|
||||
return workspaceRelationMetadataArgsCollection
|
||||
.filter((workspaceRelationMetadataArgs) => {
|
||||
// We're not storing many-to-one relations in the DB for the moment
|
||||
if (
|
||||
workspaceRelationMetadataArgs.type ===
|
||||
RelationMetadataType.MANY_TO_ONE
|
||||
) {
|
||||
if (workspaceRelationMetadataArgs.type === RelationType.MANY_TO_ONE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !isGatedAndNotEnabled(
|
||||
workspaceRelationMetadataArgs.gate,
|
||||
workspaceFeatureFlagsMap,
|
||||
context.featureFlags,
|
||||
);
|
||||
})
|
||||
.map((workspaceRelationMetadataArgs) => {
|
||||
@ -163,7 +155,9 @@ export class StandardRelationFactory {
|
||||
);
|
||||
|
||||
return {
|
||||
relationType: workspaceRelationMetadataArgs.type,
|
||||
// TODO: Will be removed when we drop RelationMetadata
|
||||
relationType:
|
||||
workspaceRelationMetadataArgs.type as unknown as RelationMetadataType,
|
||||
fromObjectMetadataId: fromObjectMetadata?.id,
|
||||
toObjectMetadataId: toObjectMetadata?.id,
|
||||
fromFieldMetadataId: fromFieldMetadata?.id,
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
|
||||
import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
||||
@ -52,6 +54,20 @@ export type FieldComparatorResult =
|
||||
>
|
||||
| ComparatorDeleteResult<FieldMetadataEntity>;
|
||||
|
||||
export type FieldRelationComparatorResult =
|
||||
| ComparatorSkipResult
|
||||
| ComparatorCreateResult<
|
||||
Partial<ComputedPartialFieldMetadata<FieldMetadataType.RELATION>> & {
|
||||
id: string;
|
||||
}
|
||||
>
|
||||
| ComparatorUpdateResult<
|
||||
Partial<ComputedPartialFieldMetadata<FieldMetadataType.RELATION>> & {
|
||||
id: string;
|
||||
}
|
||||
>
|
||||
| ComparatorDeleteResult<FieldMetadataEntity<FieldMetadataType.RELATION>>;
|
||||
|
||||
export type RelationComparatorResult =
|
||||
| ComparatorCreateResult<Partial<RelationMetadataEntity>>
|
||||
| ComparatorDeleteResult<RelationMetadataEntity>
|
||||
|
||||
@ -5,8 +5,10 @@ import { WorkspaceDynamicRelationMetadataArgsFactory } from 'src/engine/twenty-o
|
||||
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
|
||||
export type PartialFieldMetadata = Omit<
|
||||
FieldMetadataInterface,
|
||||
export type PartialFieldMetadata<
|
||||
T extends FieldMetadataType = FieldMetadataType,
|
||||
> = Omit<
|
||||
FieldMetadataInterface<T>,
|
||||
'id' | 'label' | 'description' | 'objectMetadataId'
|
||||
> & {
|
||||
standardId: string;
|
||||
@ -31,6 +33,10 @@ export type PartialComputedFieldMetadata = {
|
||||
objectMetadataId?: string;
|
||||
};
|
||||
|
||||
export type ComputedPartialFieldMetadata = {
|
||||
[K in keyof PartialFieldMetadata]: ExcludeFunctions<PartialFieldMetadata[K]>;
|
||||
export type ComputedPartialFieldMetadata<
|
||||
T extends FieldMetadataType = FieldMetadataType,
|
||||
> = {
|
||||
[K in keyof PartialFieldMetadata<T>]: ExcludeFunctions<
|
||||
PartialFieldMetadata<T>[K]
|
||||
>;
|
||||
};
|
||||
|
||||
@ -0,0 +1 @@
|
||||
export type UpdaterAction = 'create' | 'update' | 'delete';
|
||||
@ -0,0 +1,5 @@
|
||||
import { UpdaterAction } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/updater-action.type';
|
||||
|
||||
export interface UpdaterOptions {
|
||||
actions: UpdaterAction[];
|
||||
}
|
||||
@ -1,4 +1,7 @@
|
||||
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
|
||||
|
||||
export interface WorkspaceSyncContext {
|
||||
workspaceId: string;
|
||||
dataSourceId: string;
|
||||
featureFlags: FeatureFlagMap;
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { capitalize } from 'twenty-shared/utils';
|
||||
import {
|
||||
EntityManager,
|
||||
EntityTarget,
|
||||
@ -10,12 +12,13 @@ import {
|
||||
} from 'typeorm';
|
||||
import { DeepPartial } from 'typeorm/common/DeepPartial';
|
||||
import { v4 as uuidV4 } from 'uuid';
|
||||
import { capitalize } from 'twenty-shared/utils';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { PartialFieldMetadata } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-field-metadata.interface';
|
||||
import { PartialIndexMetadata } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-index-metadata.interface';
|
||||
import { UpdaterOptions } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/updater-options.interface';
|
||||
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
||||
import { FieldMetadataComplexOption } from 'src/engine/metadata-modules/field-metadata/dtos/options.input';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
@ -25,63 +28,77 @@ import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
||||
import { CompositeFieldMetadataType } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory';
|
||||
import { isFieldMetadataEntityOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
|
||||
import { FieldMetadataUpdate } from 'src/engine/workspace-manager/workspace-migration-builder/factories/workspace-migration-field.factory';
|
||||
import { ObjectMetadataUpdate } from 'src/engine/workspace-manager/workspace-migration-builder/factories/workspace-migration-object.factory';
|
||||
import { WorkspaceSyncStorage } from 'src/engine/workspace-manager/workspace-sync-metadata/storage/workspace-sync.storage';
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceMetadataUpdaterService {
|
||||
constructor(private readonly featureFlagService: FeatureFlagService) {}
|
||||
|
||||
async updateObjectMetadata(
|
||||
manager: EntityManager,
|
||||
storage: WorkspaceSyncStorage,
|
||||
options?: UpdaterOptions,
|
||||
): Promise<{
|
||||
createdObjectMetadataCollection: ObjectMetadataEntity[];
|
||||
updatedObjectMetadataCollection: ObjectMetadataUpdate[];
|
||||
}> {
|
||||
const objectMetadataRepository =
|
||||
manager.getRepository(ObjectMetadataEntity);
|
||||
let createdObjectMetadataCollection: ObjectMetadataEntity[] = [];
|
||||
let updatedObjectMetadataCollection: ObjectMetadataUpdate[] = [];
|
||||
|
||||
/**
|
||||
* Create object metadata
|
||||
*/
|
||||
const createdPartialObjectMetadataCollection =
|
||||
await objectMetadataRepository.save(
|
||||
storage.objectMetadataCreateCollection.map((objectMetadata) => ({
|
||||
...objectMetadata,
|
||||
isActive: true,
|
||||
})) as DeepPartial<ObjectMetadataEntity>[],
|
||||
if (!options || options.actions.includes('create')) {
|
||||
const createdPartialObjectMetadataCollection =
|
||||
await objectMetadataRepository.save(
|
||||
storage.objectMetadataCreateCollection.map((objectMetadata) => ({
|
||||
...objectMetadata,
|
||||
isActive: true,
|
||||
})) as DeepPartial<ObjectMetadataEntity>[],
|
||||
);
|
||||
const identifiers = createdPartialObjectMetadataCollection.map(
|
||||
(object) => object.id,
|
||||
);
|
||||
const identifiers = createdPartialObjectMetadataCollection.map(
|
||||
(object) => object.id,
|
||||
);
|
||||
const createdObjectMetadataCollection = await manager.find(
|
||||
ObjectMetadataEntity,
|
||||
{
|
||||
where: { id: In(identifiers) },
|
||||
relations: ['dataSource', 'fields'],
|
||||
},
|
||||
);
|
||||
|
||||
createdObjectMetadataCollection = await manager.find(
|
||||
ObjectMetadataEntity,
|
||||
{
|
||||
where: { id: In(identifiers) },
|
||||
relations: ['dataSource', 'fields'],
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update object metadata
|
||||
*/
|
||||
const updatedObjectMetadataCollection = await this.updateEntities(
|
||||
manager,
|
||||
ObjectMetadataEntity,
|
||||
storage.objectMetadataUpdateCollection,
|
||||
[
|
||||
'fields',
|
||||
'dataSourceId',
|
||||
'workspaceId',
|
||||
'labelIdentifierFieldMetadataId',
|
||||
'imageIdentifierFieldMetadataId',
|
||||
],
|
||||
);
|
||||
if (!options || options.actions.includes('update')) {
|
||||
updatedObjectMetadataCollection = await this.updateEntities(
|
||||
manager,
|
||||
ObjectMetadataEntity,
|
||||
storage.objectMetadataUpdateCollection,
|
||||
[
|
||||
'fields',
|
||||
'dataSourceId',
|
||||
'workspaceId',
|
||||
'labelIdentifierFieldMetadataId',
|
||||
'imageIdentifierFieldMetadataId',
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete object metadata
|
||||
*/
|
||||
if (storage.objectMetadataDeleteCollection.length > 0) {
|
||||
if (
|
||||
storage.objectMetadataDeleteCollection.length > 0 &&
|
||||
(!options || options.actions.includes('delete'))
|
||||
) {
|
||||
await objectMetadataRepository.delete(
|
||||
storage.objectMetadataDeleteCollection.map((object) => object.id),
|
||||
);
|
||||
@ -121,6 +138,7 @@ export class WorkspaceMetadataUpdaterService {
|
||||
async updateFieldMetadata(
|
||||
manager: EntityManager,
|
||||
storage: WorkspaceSyncStorage,
|
||||
options?: UpdaterOptions,
|
||||
): Promise<{
|
||||
createdFieldMetadataCollection: FieldMetadataEntity[];
|
||||
updatedFieldMetadataCollection: FieldMetadataUpdate[];
|
||||
@ -130,26 +148,32 @@ export class WorkspaceMetadataUpdaterService {
|
||||
IndexFieldMetadataEntity,
|
||||
);
|
||||
const indexMetadataRepository = manager.getRepository(IndexMetadataEntity);
|
||||
let createdFieldMetadataCollection: FieldMetadataEntity[] = [];
|
||||
let updatedFieldMetadataCollection: FieldMetadataUpdate[] = [];
|
||||
|
||||
/**
|
||||
* Update field metadata
|
||||
*/
|
||||
const updatedFieldMetadataCollection =
|
||||
await this.updateEntities<FieldMetadataEntity>(
|
||||
manager,
|
||||
FieldMetadataEntity,
|
||||
storage.fieldMetadataUpdateCollection,
|
||||
['objectMetadataId', 'workspaceId'],
|
||||
);
|
||||
if (!options || options.actions.includes('update')) {
|
||||
updatedFieldMetadataCollection =
|
||||
await this.updateEntities<FieldMetadataEntity>(
|
||||
manager,
|
||||
FieldMetadataEntity,
|
||||
storage.fieldMetadataUpdateCollection,
|
||||
['objectMetadataId', 'workspaceId'],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create field metadata
|
||||
*/
|
||||
const createdFieldMetadataCollection = await fieldMetadataRepository.save(
|
||||
storage.fieldMetadataCreateCollection.map((field) =>
|
||||
this.prepareFieldMetadataForCreation(field),
|
||||
) as DeepPartial<FieldMetadataEntity>[],
|
||||
);
|
||||
if (!options || options.actions.includes('create')) {
|
||||
createdFieldMetadataCollection = await fieldMetadataRepository.save(
|
||||
storage.fieldMetadataCreateCollection.map((field) =>
|
||||
this.prepareFieldMetadataForCreation(field),
|
||||
) as DeepPartial<FieldMetadataEntity>[],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete field metadata
|
||||
@ -160,7 +184,10 @@ export class WorkspaceMetadataUpdaterService {
|
||||
(field) => field.type !== FieldMetadataType.RELATION,
|
||||
);
|
||||
|
||||
if (fieldMetadataDeleteCollectionWithoutRelationType.length > 0) {
|
||||
if (
|
||||
fieldMetadataDeleteCollectionWithoutRelationType.length > 0 &&
|
||||
(!options || options.actions.includes('delete'))
|
||||
) {
|
||||
await this.deleteIndexFieldMetadata(
|
||||
fieldMetadataDeleteCollectionWithoutRelationType,
|
||||
indexFieldMetadataRepository,
|
||||
@ -181,6 +208,68 @@ export class WorkspaceMetadataUpdaterService {
|
||||
};
|
||||
}
|
||||
|
||||
async updateFieldRelationMetadata(
|
||||
manager: EntityManager,
|
||||
storage: WorkspaceSyncStorage,
|
||||
options?: UpdaterOptions,
|
||||
): Promise<{
|
||||
createdFieldRelationMetadataCollection: FieldMetadataUpdate<FieldMetadataType.RELATION>[];
|
||||
updatedFieldRelationMetadataCollection: FieldMetadataUpdate<FieldMetadataType.RELATION>[];
|
||||
deletedFieldRelationMetadataCollection: FieldMetadataUpdate<FieldMetadataType.RELATION>[];
|
||||
}> {
|
||||
let createdFieldRelationMetadataCollection: FieldMetadataUpdate<FieldMetadataType.RELATION>[] =
|
||||
[];
|
||||
let updatedFieldRelationMetadataCollection: FieldMetadataUpdate<FieldMetadataType.RELATION>[] =
|
||||
[];
|
||||
let deletedFieldRelationMetadataCollection: FieldMetadataUpdate<FieldMetadataType.RELATION>[] =
|
||||
[];
|
||||
|
||||
/**
|
||||
* Create field relation metadata
|
||||
*/
|
||||
if (!options || options.actions.includes('create')) {
|
||||
createdFieldRelationMetadataCollection = await this.updateEntities<
|
||||
FieldMetadataEntity<FieldMetadataType.RELATION>
|
||||
>(
|
||||
manager,
|
||||
FieldMetadataEntity,
|
||||
storage.fieldRelationMetadataCreateCollection,
|
||||
['objectMetadataId', 'workspaceId'],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update field relation metadata
|
||||
*/
|
||||
if (!options || options.actions.includes('update')) {
|
||||
updatedFieldRelationMetadataCollection = await this.updateEntities<
|
||||
FieldMetadataEntity<FieldMetadataType.RELATION>
|
||||
>(
|
||||
manager,
|
||||
FieldMetadataEntity,
|
||||
storage.fieldRelationMetadataUpdateCollection,
|
||||
['objectMetadataId', 'workspaceId'],
|
||||
);
|
||||
}
|
||||
|
||||
if (!options || options.actions.includes('delete')) {
|
||||
deletedFieldRelationMetadataCollection = await this.updateEntities<
|
||||
FieldMetadataEntity<FieldMetadataType.RELATION>
|
||||
>(
|
||||
manager,
|
||||
FieldMetadataEntity,
|
||||
storage.fieldRelationMetadataDeleteCollection,
|
||||
['objectMetadataId', 'workspaceId'],
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
createdFieldRelationMetadataCollection,
|
||||
updatedFieldRelationMetadataCollection,
|
||||
deletedFieldRelationMetadataCollection,
|
||||
};
|
||||
}
|
||||
|
||||
async deleteIndexFieldMetadata(
|
||||
fieldMetadataDeleteCollectionWithoutRelationType: Partial<FieldMetadataEntity>[],
|
||||
indexFieldMetadataRepository: Repository<IndexFieldMetadataEntity>,
|
||||
@ -276,6 +365,15 @@ export class WorkspaceMetadataUpdaterService {
|
||||
createdIndexMetadataCollection: IndexMetadataEntity[];
|
||||
}> {
|
||||
const indexMetadataRepository = manager.getRepository(IndexMetadataEntity);
|
||||
const workspaceId = originalObjectMetadataCollection?.[0]?.workspaceId;
|
||||
let isNewRelationEnabled = false;
|
||||
|
||||
if (workspaceId) {
|
||||
isNewRelationEnabled = await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IsNewRelationEnabled,
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
const convertIndexMetadataForSaving = (
|
||||
indexMetadata: PartialIndexMetadata,
|
||||
@ -288,6 +386,15 @@ export class WorkspaceMetadataUpdaterService {
|
||||
const fieldMetadata = originalObjectMetadataCollection
|
||||
.find((object) => object.id === indexMetadata.objectMetadataId)
|
||||
?.fields.find((field) => {
|
||||
if (
|
||||
isNewRelationEnabled &&
|
||||
isFieldMetadataEntityOfType(field, FieldMetadataType.RELATION)
|
||||
) {
|
||||
if (field.settings?.joinColumnName === column) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (field.name === column) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -0,0 +1,267 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { EntityManager } from 'typeorm';
|
||||
|
||||
import { WorkspaceMigrationBuilderAction } from 'src/engine/workspace-manager/workspace-migration-builder/interfaces/workspace-migration-builder-action.interface';
|
||||
import {
|
||||
ComparatorAction,
|
||||
FieldRelationComparatorResult,
|
||||
} from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/comparator.interface';
|
||||
import { WorkspaceSyncContext } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/workspace-sync-context.interface';
|
||||
|
||||
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 { WorkspaceMigrationEntity } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
|
||||
import { CustomWorkspaceEntity } from 'src/engine/twenty-orm/custom.workspace-entity';
|
||||
import { WorkspaceMigrationFieldRelationFactory } from 'src/engine/workspace-manager/workspace-migration-builder/factories/workspace-migration-field-relation.factory';
|
||||
import {
|
||||
FieldMetadataUpdate,
|
||||
WorkspaceMigrationFieldFactory,
|
||||
} from 'src/engine/workspace-manager/workspace-migration-builder/factories/workspace-migration-field.factory';
|
||||
import { WorkspaceFieldRelationComparator } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/workspace-field-relation.comparator';
|
||||
import { StandardFieldRelationFactory } from 'src/engine/workspace-manager/workspace-sync-metadata/factories/standard-field-relation.factory';
|
||||
import { WorkspaceMetadataUpdaterService } from 'src/engine/workspace-manager/workspace-sync-metadata/services/workspace-metadata-updater.service';
|
||||
import { standardObjectMetadataDefinitions } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-objects';
|
||||
import { WorkspaceSyncStorage } from 'src/engine/workspace-manager/workspace-sync-metadata/storage/workspace-sync.storage';
|
||||
import { mapObjectMetadataByUniqueIdentifier } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/sync-metadata.util';
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceSyncFieldMetadataRelationService {
|
||||
private readonly logger = new Logger(
|
||||
WorkspaceSyncFieldMetadataRelationService.name,
|
||||
);
|
||||
|
||||
constructor(
|
||||
private readonly standardFieldRelationFactory: StandardFieldRelationFactory,
|
||||
private readonly workspaceFieldRelationComparator: WorkspaceFieldRelationComparator,
|
||||
private readonly workspaceMetadataUpdaterService: WorkspaceMetadataUpdaterService,
|
||||
private readonly workspaceMigrationFieldFactory: WorkspaceMigrationFieldFactory,
|
||||
private readonly workspaceMigrationFieldRelationFactory: WorkspaceMigrationFieldRelationFactory,
|
||||
) {}
|
||||
|
||||
async synchronize(
|
||||
context: WorkspaceSyncContext,
|
||||
manager: EntityManager,
|
||||
storage: WorkspaceSyncStorage,
|
||||
): Promise<Partial<WorkspaceMigrationEntity>[]> {
|
||||
let originalObjectMetadataCollection =
|
||||
await this.getOriginalObjectMetadataCollection(context, manager);
|
||||
|
||||
const customObjectMetadataCollection =
|
||||
originalObjectMetadataCollection.filter(
|
||||
(objectMetadata) => objectMetadata.isCustom,
|
||||
);
|
||||
|
||||
// Create map of object metadata & field metadata by unique identifier
|
||||
const originalObjectMetadataMapByName = mapObjectMetadataByUniqueIdentifier(
|
||||
originalObjectMetadataCollection,
|
||||
// Relation are based on the singular name
|
||||
(objectMetadata) => objectMetadata.nameSingular,
|
||||
);
|
||||
|
||||
await this.synchronizeStandardObjectRelationFields(
|
||||
context,
|
||||
originalObjectMetadataCollection,
|
||||
originalObjectMetadataMapByName,
|
||||
storage,
|
||||
);
|
||||
|
||||
await this.synchronizeCustomObjectRelationFields(
|
||||
context,
|
||||
customObjectMetadataCollection,
|
||||
originalObjectMetadataMapByName,
|
||||
storage,
|
||||
);
|
||||
|
||||
this.logger.log('Updating workspace metadata');
|
||||
|
||||
// Save field metadata to DB
|
||||
const metadataFieldUpdaterResult =
|
||||
await this.workspaceMetadataUpdaterService.updateFieldRelationMetadata(
|
||||
manager,
|
||||
storage,
|
||||
);
|
||||
|
||||
this.logger.log('Generating migrations');
|
||||
|
||||
const updateFieldWorkspaceMigrations =
|
||||
await this.workspaceMigrationFieldFactory.create(
|
||||
originalObjectMetadataCollection,
|
||||
[
|
||||
// Both of them are update as field where created in WorkspaceSyncFieldMetadataService
|
||||
...metadataFieldUpdaterResult.createdFieldRelationMetadataCollection,
|
||||
...metadataFieldUpdaterResult.updatedFieldRelationMetadataCollection,
|
||||
...metadataFieldUpdaterResult.deletedFieldRelationMetadataCollection,
|
||||
] as FieldMetadataUpdate[],
|
||||
WorkspaceMigrationBuilderAction.UPDATE,
|
||||
);
|
||||
|
||||
// Resync updated object metadata
|
||||
originalObjectMetadataCollection =
|
||||
await this.getOriginalObjectMetadataCollection(context, manager);
|
||||
|
||||
const deletedFieldRelationMetadataCollection =
|
||||
metadataFieldUpdaterResult.deletedFieldRelationMetadataCollection.map(
|
||||
(field) => field.altered,
|
||||
);
|
||||
|
||||
const deleteFieldRelationWorkspaceMigrations =
|
||||
await this.workspaceMigrationFieldRelationFactory.create(
|
||||
originalObjectMetadataCollection,
|
||||
deletedFieldRelationMetadataCollection,
|
||||
WorkspaceMigrationBuilderAction.DELETE,
|
||||
);
|
||||
|
||||
const createdFieldRelationMetadataCollection =
|
||||
metadataFieldUpdaterResult.createdFieldRelationMetadataCollection.map(
|
||||
(field) => field.altered,
|
||||
);
|
||||
|
||||
const createFieldRelationWorkspaceMigrations =
|
||||
await this.workspaceMigrationFieldRelationFactory.create(
|
||||
originalObjectMetadataCollection,
|
||||
createdFieldRelationMetadataCollection,
|
||||
WorkspaceMigrationBuilderAction.CREATE,
|
||||
);
|
||||
|
||||
const updateFieldRelationWorkspaceMigrations =
|
||||
await this.workspaceMigrationFieldRelationFactory.create(
|
||||
originalObjectMetadataCollection,
|
||||
metadataFieldUpdaterResult.updatedFieldRelationMetadataCollection,
|
||||
WorkspaceMigrationBuilderAction.UPDATE,
|
||||
);
|
||||
|
||||
// TODO: Should we handle deletion of relation here?
|
||||
|
||||
this.logger.log('Saving migrations');
|
||||
|
||||
return [
|
||||
...updateFieldWorkspaceMigrations,
|
||||
...deleteFieldRelationWorkspaceMigrations,
|
||||
...createFieldRelationWorkspaceMigrations,
|
||||
...updateFieldRelationWorkspaceMigrations,
|
||||
];
|
||||
}
|
||||
|
||||
private async synchronizeStandardObjectRelationFields(
|
||||
context: WorkspaceSyncContext,
|
||||
originalObjectMetadataCollection: ObjectMetadataEntity[],
|
||||
originalObjectMetadataMapByName: Record<string, ObjectMetadataEntity>,
|
||||
storage: WorkspaceSyncStorage,
|
||||
): Promise<void> {
|
||||
// Create standard field metadata map
|
||||
const standardFieldMetadataRelationCollection =
|
||||
this.standardFieldRelationFactory.createFieldRelationForStandardObject(
|
||||
standardObjectMetadataDefinitions,
|
||||
context,
|
||||
originalObjectMetadataMapByName,
|
||||
);
|
||||
|
||||
// Create map of original and standard object metadata by standard ids
|
||||
const originalObjectMetadataMap = mapObjectMetadataByUniqueIdentifier(
|
||||
originalObjectMetadataCollection,
|
||||
);
|
||||
|
||||
// Loop over all standard objects and compare them with the objects in DB
|
||||
for (const [
|
||||
standardObjectId,
|
||||
standardFieldMetadataCollection,
|
||||
] of standardFieldMetadataRelationCollection) {
|
||||
const originalObjectMetadata =
|
||||
originalObjectMetadataMap[standardObjectId];
|
||||
|
||||
const originalFieldRelationMetadataCollection =
|
||||
(originalObjectMetadata?.fields.filter(
|
||||
(field) => field.type === FieldMetadataType.RELATION,
|
||||
) ?? []) as FieldMetadataEntity<FieldMetadataType.RELATION>[];
|
||||
|
||||
if (originalFieldRelationMetadataCollection.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const fieldComparatorResults =
|
||||
this.workspaceFieldRelationComparator.compare(
|
||||
originalFieldRelationMetadataCollection,
|
||||
standardFieldMetadataCollection,
|
||||
);
|
||||
|
||||
this.storeComparatorResults(fieldComparatorResults, storage);
|
||||
}
|
||||
}
|
||||
|
||||
private async synchronizeCustomObjectRelationFields(
|
||||
context: WorkspaceSyncContext,
|
||||
customObjectMetadataCollection: ObjectMetadataEntity[],
|
||||
originalObjectMetadataMapByName: Record<string, ObjectMetadataEntity>,
|
||||
storage: WorkspaceSyncStorage,
|
||||
): Promise<void> {
|
||||
// Create standard field metadata collection
|
||||
const customFieldMetadataRelationCollection =
|
||||
this.standardFieldRelationFactory.createFieldRelationForCustomObject(
|
||||
customObjectMetadataCollection.map((objectMetadata) => ({
|
||||
object: objectMetadata,
|
||||
metadata: CustomWorkspaceEntity,
|
||||
})),
|
||||
context,
|
||||
originalObjectMetadataMapByName,
|
||||
);
|
||||
|
||||
// Loop over all custom objects from the DB and compare their fields with standard fields
|
||||
for (const customObjectMetadata of customObjectMetadataCollection) {
|
||||
/**
|
||||
* COMPARE FIELD METADATA
|
||||
*/
|
||||
const fieldComparatorResults =
|
||||
this.workspaceFieldRelationComparator.compare(
|
||||
customObjectMetadata.fields as FieldMetadataEntity<FieldMetadataType.RELATION>[],
|
||||
customFieldMetadataRelationCollection,
|
||||
);
|
||||
|
||||
this.storeComparatorResults(fieldComparatorResults, storage);
|
||||
}
|
||||
}
|
||||
|
||||
private async getOriginalObjectMetadataCollection(
|
||||
context: WorkspaceSyncContext,
|
||||
manager: EntityManager,
|
||||
): Promise<ObjectMetadataEntity[]> {
|
||||
const objectMetadataRepository =
|
||||
manager.getRepository(ObjectMetadataEntity);
|
||||
|
||||
const originalObjectMetadataCollection =
|
||||
await objectMetadataRepository.find({
|
||||
where: {
|
||||
workspaceId: context.workspaceId,
|
||||
fields: {
|
||||
type: FieldMetadataType.RELATION,
|
||||
},
|
||||
},
|
||||
relations: ['dataSource', 'fields'],
|
||||
});
|
||||
|
||||
return originalObjectMetadataCollection;
|
||||
}
|
||||
|
||||
private storeComparatorResults(
|
||||
fieldComparatorResults: FieldRelationComparatorResult[],
|
||||
storage: WorkspaceSyncStorage,
|
||||
): void {
|
||||
for (const fieldComparatorResult of fieldComparatorResults) {
|
||||
switch (fieldComparatorResult.action) {
|
||||
case ComparatorAction.CREATE: {
|
||||
storage.addCreateFieldRelationMetadata(fieldComparatorResult.object);
|
||||
break;
|
||||
}
|
||||
case ComparatorAction.UPDATE: {
|
||||
storage.addUpdateFieldRelationMetadata(fieldComparatorResult.object);
|
||||
break;
|
||||
}
|
||||
case ComparatorAction.DELETE: {
|
||||
storage.addDeleteFieldRelationMetadata(fieldComparatorResult.object);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,7 +2,6 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { EntityManager } from 'typeorm';
|
||||
|
||||
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
|
||||
import { WorkspaceMigrationBuilderAction } from 'src/engine/workspace-manager/workspace-migration-builder/interfaces/workspace-migration-builder-action.interface';
|
||||
import {
|
||||
ComparatorAction,
|
||||
@ -37,7 +36,6 @@ export class WorkspaceSyncFieldMetadataService {
|
||||
context: WorkspaceSyncContext,
|
||||
manager: EntityManager,
|
||||
storage: WorkspaceSyncStorage,
|
||||
workspaceFeatureFlagsMap: FeatureFlagMap,
|
||||
): Promise<Partial<WorkspaceMigrationEntity>[]> {
|
||||
const objectMetadataRepository =
|
||||
manager.getRepository(ObjectMetadataEntity);
|
||||
@ -61,14 +59,12 @@ export class WorkspaceSyncFieldMetadataService {
|
||||
originalObjectMetadataCollection,
|
||||
customObjectMetadataCollection,
|
||||
storage,
|
||||
workspaceFeatureFlagsMap,
|
||||
);
|
||||
|
||||
await this.synchronizeCustomObjectFields(
|
||||
context,
|
||||
customObjectMetadataCollection,
|
||||
storage,
|
||||
workspaceFeatureFlagsMap,
|
||||
);
|
||||
|
||||
this.logger.log('Updating workspace metadata');
|
||||
@ -121,14 +117,12 @@ export class WorkspaceSyncFieldMetadataService {
|
||||
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
|
||||
@ -145,6 +139,7 @@ export class WorkspaceSyncFieldMetadataService {
|
||||
originalObjectMetadataMap[standardObjectId];
|
||||
|
||||
const computedStandardFieldMetadataCollection = computeStandardFields(
|
||||
context,
|
||||
standardFieldMetadataCollection,
|
||||
originalObjectMetadata,
|
||||
// We need to provide this for generated relations with custom objects
|
||||
@ -161,24 +156,46 @@ export class WorkspaceSyncFieldMetadataService {
|
||||
}
|
||||
}
|
||||
|
||||
synchronizeCustomObject(
|
||||
context: WorkspaceSyncContext,
|
||||
customObjectMetadata: ObjectMetadataEntity,
|
||||
): FieldComparatorResult[] {
|
||||
// Create standard field metadata collection
|
||||
const customObjectStandardFieldMetadataCollection =
|
||||
this.standardFieldFactory.create(CustomWorkspaceEntity, context);
|
||||
|
||||
const standardFieldMetadataCollection = computeStandardFields(
|
||||
context,
|
||||
customObjectStandardFieldMetadataCollection,
|
||||
customObjectMetadata,
|
||||
);
|
||||
|
||||
/**
|
||||
* COMPARE FIELD METADATA
|
||||
*/
|
||||
const fieldComparatorResults = this.workspaceFieldComparator.compare(
|
||||
customObjectMetadata.id,
|
||||
customObjectMetadata.fields,
|
||||
standardFieldMetadataCollection,
|
||||
);
|
||||
|
||||
return fieldComparatorResults;
|
||||
}
|
||||
|
||||
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,
|
||||
);
|
||||
this.standardFieldFactory.create(CustomWorkspaceEntity, context);
|
||||
|
||||
// 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(
|
||||
context,
|
||||
customObjectStandardFieldMetadataCollection,
|
||||
customObjectMetadata,
|
||||
);
|
||||
|
||||
@ -2,7 +2,6 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { Any, EntityManager } from 'typeorm';
|
||||
|
||||
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
|
||||
import { WorkspaceMigrationBuilderAction } from 'src/engine/workspace-manager/workspace-migration-builder/interfaces/workspace-migration-builder-action.interface';
|
||||
import { ComparatorAction } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/comparator.interface';
|
||||
import { WorkspaceSyncContext } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/workspace-sync-context.interface';
|
||||
@ -33,7 +32,6 @@ export class WorkspaceSyncIndexMetadataService {
|
||||
context: WorkspaceSyncContext,
|
||||
manager: EntityManager,
|
||||
storage: WorkspaceSyncStorage,
|
||||
workspaceFeatureFlagsMap: FeatureFlagMap,
|
||||
): Promise<Partial<WorkspaceMigrationEntity>[]> {
|
||||
this.logger.log('Syncing index metadata');
|
||||
|
||||
@ -89,7 +87,6 @@ export class WorkspaceSyncIndexMetadataService {
|
||||
context,
|
||||
originalStandardObjectMetadataMap,
|
||||
originalCustomObjectMetadataMap,
|
||||
workspaceFeatureFlagsMap,
|
||||
);
|
||||
|
||||
const indexComparatorResults = this.workspaceIndexComparator.compare(
|
||||
|
||||
@ -3,7 +3,6 @@ import { Injectable } from '@nestjs/common';
|
||||
import { EntityManager, Repository } from 'typeorm';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
|
||||
import { WorkspaceSyncContext } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/workspace-sync-context.interface';
|
||||
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
@ -21,7 +20,6 @@ export class WorkspaceSyncObjectMetadataIdentifiersService {
|
||||
context: WorkspaceSyncContext,
|
||||
manager: EntityManager,
|
||||
_storage: WorkspaceSyncStorage,
|
||||
workspaceFeatureFlagsMap: FeatureFlagMap,
|
||||
): Promise<void> {
|
||||
const objectMetadataRepository =
|
||||
manager.getRepository(ObjectMetadataEntity);
|
||||
@ -32,10 +30,8 @@ export class WorkspaceSyncObjectMetadataIdentifiersService {
|
||||
objectMetadataRepository,
|
||||
);
|
||||
|
||||
const standardObjectMetadataMap = this.createStandardObjectMetadataMap(
|
||||
context,
|
||||
workspaceFeatureFlagsMap,
|
||||
);
|
||||
const standardObjectMetadataMap =
|
||||
this.createStandardObjectMetadataMap(context);
|
||||
|
||||
await this.processObjectMetadataCollection(
|
||||
originalObjectMetadataCollection,
|
||||
@ -56,12 +52,10 @@ export class WorkspaceSyncObjectMetadataIdentifiersService {
|
||||
|
||||
private createStandardObjectMetadataMap(
|
||||
context: WorkspaceSyncContext,
|
||||
workspaceFeatureFlagsMap: FeatureFlagMap,
|
||||
): Record<string, any> {
|
||||
const standardObjectMetadataCollection = this.standardObjectFactory.create(
|
||||
standardObjectMetadataDefinitions,
|
||||
context,
|
||||
workspaceFeatureFlagsMap,
|
||||
);
|
||||
|
||||
return mapObjectMetadataByUniqueIdentifier(
|
||||
|
||||
@ -2,7 +2,6 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { EntityManager } from 'typeorm';
|
||||
|
||||
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
|
||||
import { WorkspaceMigrationBuilderAction } from 'src/engine/workspace-manager/workspace-migration-builder/interfaces/workspace-migration-builder-action.interface';
|
||||
import { ComparatorAction } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/comparator.interface';
|
||||
import { WorkspaceSyncContext } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/workspace-sync-context.interface';
|
||||
@ -33,7 +32,6 @@ export class WorkspaceSyncObjectMetadataService {
|
||||
context: WorkspaceSyncContext,
|
||||
manager: EntityManager,
|
||||
storage: WorkspaceSyncStorage,
|
||||
workspaceFeatureFlagsMap: FeatureFlagMap,
|
||||
): Promise<Partial<WorkspaceMigrationEntity>[]> {
|
||||
const objectMetadataRepository =
|
||||
manager.getRepository(ObjectMetadataEntity);
|
||||
@ -83,7 +81,6 @@ export class WorkspaceSyncObjectMetadataService {
|
||||
const standardObjectMetadataCollection = this.standardObjectFactory.create(
|
||||
standardObjectMetadataDefinitions,
|
||||
context,
|
||||
workspaceFeatureFlagsMap,
|
||||
);
|
||||
|
||||
// Create map of original and standard object metadata by standard ids
|
||||
|
||||
@ -2,7 +2,6 @@ import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { EntityManager } from 'typeorm';
|
||||
|
||||
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
|
||||
import { WorkspaceMigrationBuilderAction } from 'src/engine/workspace-manager/workspace-migration-builder/interfaces/workspace-migration-builder-action.interface';
|
||||
import { ComparatorAction } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/comparator.interface';
|
||||
import { WorkspaceSyncContext } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/workspace-sync-context.interface';
|
||||
@ -32,7 +31,6 @@ export class WorkspaceSyncRelationMetadataService {
|
||||
context: WorkspaceSyncContext,
|
||||
manager: EntityManager,
|
||||
storage: WorkspaceSyncStorage,
|
||||
workspaceFeatureFlagsMap: FeatureFlagMap,
|
||||
): Promise<Partial<WorkspaceMigrationEntity>[]> {
|
||||
const objectMetadataRepository =
|
||||
manager.getRepository(ObjectMetadataEntity);
|
||||
@ -76,7 +74,6 @@ export class WorkspaceSyncRelationMetadataService {
|
||||
standardObjectMetadataDefinitions,
|
||||
context,
|
||||
originalObjectMetadataMap,
|
||||
workspaceFeatureFlagsMap,
|
||||
);
|
||||
|
||||
const customRelationMetadataCollection =
|
||||
@ -87,7 +84,6 @@ export class WorkspaceSyncRelationMetadataService {
|
||||
})),
|
||||
context,
|
||||
originalObjectMetadataMap,
|
||||
workspaceFeatureFlagsMap,
|
||||
);
|
||||
|
||||
const relationComparatorResults = this.workspaceRelationComparator.compare(
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { ComputedPartialFieldMetadata } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-field-metadata.interface';
|
||||
import { ComputedPartialWorkspaceEntity } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-object-metadata.interface';
|
||||
|
||||
@ -27,6 +29,20 @@ export class WorkspaceSyncStorage {
|
||||
})[] = [];
|
||||
private readonly _fieldMetadataDeleteCollection: FieldMetadataEntity[] = [];
|
||||
|
||||
// Field relation metadata
|
||||
private readonly _fieldRelationMetadataCreateCollection: (Partial<
|
||||
ComputedPartialFieldMetadata<FieldMetadataType.RELATION>
|
||||
> & {
|
||||
id: string;
|
||||
})[] = [];
|
||||
private readonly _fieldRelationMetadataUpdateCollection: (Partial<
|
||||
ComputedPartialFieldMetadata<FieldMetadataType.RELATION>
|
||||
> & {
|
||||
id: string;
|
||||
})[] = [];
|
||||
private readonly _fieldRelationMetadataDeleteCollection: FieldMetadataEntity<FieldMetadataType.RELATION>[] =
|
||||
[];
|
||||
|
||||
// Relation metadata
|
||||
private readonly _relationMetadataCreateCollection: Partial<RelationMetadataEntity>[] =
|
||||
[];
|
||||
@ -68,6 +84,18 @@ export class WorkspaceSyncStorage {
|
||||
return this._fieldMetadataDeleteCollection;
|
||||
}
|
||||
|
||||
get fieldRelationMetadataCreateCollection() {
|
||||
return this._fieldRelationMetadataCreateCollection;
|
||||
}
|
||||
|
||||
get fieldRelationMetadataUpdateCollection() {
|
||||
return this._fieldRelationMetadataUpdateCollection;
|
||||
}
|
||||
|
||||
get fieldRelationMetadataDeleteCollection() {
|
||||
return this._fieldRelationMetadataDeleteCollection;
|
||||
}
|
||||
|
||||
get relationMetadataCreateCollection() {
|
||||
return this._relationMetadataCreateCollection;
|
||||
}
|
||||
@ -118,6 +146,28 @@ export class WorkspaceSyncStorage {
|
||||
this._fieldMetadataDeleteCollection.push(field);
|
||||
}
|
||||
|
||||
addCreateFieldRelationMetadata(
|
||||
field: Partial<ComputedPartialFieldMetadata<FieldMetadataType.RELATION>> & {
|
||||
id: string;
|
||||
},
|
||||
) {
|
||||
this._fieldRelationMetadataCreateCollection.push(field);
|
||||
}
|
||||
|
||||
addUpdateFieldRelationMetadata(
|
||||
field: Partial<ComputedPartialFieldMetadata<FieldMetadataType.RELATION>> & {
|
||||
id: string;
|
||||
},
|
||||
) {
|
||||
this._fieldRelationMetadataUpdateCollection.push(field);
|
||||
}
|
||||
|
||||
addDeleteFieldRelationMetadata(
|
||||
field: FieldMetadataEntity<FieldMetadataType.RELATION>,
|
||||
) {
|
||||
this._fieldRelationMetadataDeleteCollection.push(field);
|
||||
}
|
||||
|
||||
addCreateRelationMetadata(relation: Partial<RelationMetadataEntity>) {
|
||||
this._relationMetadataCreateCollection.push(relation);
|
||||
}
|
||||
|
||||
@ -5,7 +5,9 @@ import {
|
||||
PartialComputedFieldMetadata,
|
||||
PartialFieldMetadata,
|
||||
} from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-field-metadata.interface';
|
||||
import { WorkspaceSyncContext } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/workspace-sync-context.interface';
|
||||
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import {
|
||||
createForeignKeyDeterministicUuid,
|
||||
@ -13,6 +15,7 @@ import {
|
||||
} from 'src/engine/workspace-manager/workspace-sync-metadata/utils/create-deterministic-uuid.util';
|
||||
|
||||
export const computeStandardFields = (
|
||||
context: WorkspaceSyncContext,
|
||||
standardFieldMetadataCollection: (
|
||||
| PartialFieldMetadata
|
||||
| PartialComputedFieldMetadata
|
||||
@ -22,6 +25,9 @@ export const computeStandardFields = (
|
||||
): ComputedPartialFieldMetadata[] => {
|
||||
const fields: ComputedPartialFieldMetadata[] = [];
|
||||
|
||||
const isNewRelationEnabled =
|
||||
context.featureFlags[FeatureFlagKey.IsNewRelationEnabled];
|
||||
|
||||
for (const partialFieldMetadata of standardFieldMetadataCollection) {
|
||||
// Relation from standard object to custom object
|
||||
if ('argsFactory' in partialFieldMetadata) {
|
||||
@ -52,18 +58,22 @@ export const computeStandardFields = (
|
||||
defaultValue: null,
|
||||
});
|
||||
|
||||
// Foreign key
|
||||
fields.push({
|
||||
...rest,
|
||||
standardId: foreignKeyStandardId,
|
||||
name: joinColumn,
|
||||
type: FieldMetadataType.UUID,
|
||||
label: `${data.label} ID (foreign key)`,
|
||||
description: `${data.description} id foreign key`,
|
||||
defaultValue: null,
|
||||
icon: undefined,
|
||||
isSystem: true,
|
||||
});
|
||||
// Only add foreign key if new relation is disabled
|
||||
// As new relation will no longer create the field metadata related to foreign key
|
||||
if (!isNewRelationEnabled) {
|
||||
// Foreign key
|
||||
fields.push({
|
||||
...rest,
|
||||
standardId: foreignKeyStandardId,
|
||||
name: joinColumn,
|
||||
type: FieldMetadataType.UUID,
|
||||
label: `${data.label} ID (foreign key)`,
|
||||
description: `${data.description} id foreign key`,
|
||||
defaultValue: null,
|
||||
icon: undefined,
|
||||
isSystem: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Relation from standard object to standard object
|
||||
|
||||
@ -17,6 +17,7 @@ import { SyncWorkspaceMetadataCommand } from 'src/engine/workspace-manager/works
|
||||
import { workspaceSyncMetadataComparators } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators';
|
||||
import { workspaceSyncMetadataFactories } from 'src/engine/workspace-manager/workspace-sync-metadata/factories';
|
||||
import { WorkspaceMetadataUpdaterService } from 'src/engine/workspace-manager/workspace-sync-metadata/services/workspace-metadata-updater.service';
|
||||
import { WorkspaceSyncFieldMetadataRelationService } from 'src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-field-metadata-relation.service';
|
||||
import { WorkspaceSyncFieldMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-field-metadata.service';
|
||||
import { WorkspaceSyncIndexMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-index-metadata.service';
|
||||
import { WorkspaceSyncObjectMetadataIdentifiersService } from 'src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-object-metadata-identifiers.service';
|
||||
@ -50,6 +51,7 @@ import { WorkspaceSyncMetadataService } from 'src/engine/workspace-manager/works
|
||||
WorkspaceSyncObjectMetadataIdentifiersService,
|
||||
WorkspaceSyncRelationMetadataService,
|
||||
WorkspaceSyncFieldMetadataService,
|
||||
WorkspaceSyncFieldMetadataRelationService,
|
||||
WorkspaceSyncMetadataService,
|
||||
WorkspaceSyncIndexMetadataService,
|
||||
SyncWorkspaceLoggerService,
|
||||
|
||||
@ -5,6 +5,7 @@ import { DataSource, QueryFailedError } from 'typeorm';
|
||||
|
||||
import { WorkspaceSyncContext } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/workspace-sync-context.interface';
|
||||
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service';
|
||||
import {
|
||||
@ -12,6 +13,7 @@ import {
|
||||
WorkspaceMigrationTableActionType,
|
||||
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
|
||||
import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service';
|
||||
import { WorkspaceSyncFieldMetadataRelationService } from 'src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-field-metadata-relation.service';
|
||||
import { WorkspaceSyncFieldMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-field-metadata.service';
|
||||
import { WorkspaceSyncIndexMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-index-metadata.service';
|
||||
import { WorkspaceSyncObjectMetadataIdentifiersService } from 'src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-object-metadata-identifiers.service';
|
||||
@ -30,14 +32,15 @@ export class WorkspaceSyncMetadataService {
|
||||
constructor(
|
||||
@InjectDataSource('metadata')
|
||||
private readonly metadataDataSource: DataSource,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
|
||||
private readonly workspaceSyncObjectMetadataService: WorkspaceSyncObjectMetadataService,
|
||||
private readonly workspaceSyncRelationMetadataService: WorkspaceSyncRelationMetadataService,
|
||||
private readonly workspaceSyncFieldMetadataService: WorkspaceSyncFieldMetadataService,
|
||||
private readonly workspaceSyncFieldMetadataRelationService: WorkspaceSyncFieldMetadataRelationService,
|
||||
private readonly workspaceSyncIndexMetadataService: WorkspaceSyncIndexMetadataService,
|
||||
private readonly workspaceSyncObjectMetadataIdentifiersService: WorkspaceSyncObjectMetadataIdentifiersService,
|
||||
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@ -67,16 +70,16 @@ export class WorkspaceSyncMetadataService {
|
||||
const manager = queryRunner.manager;
|
||||
|
||||
try {
|
||||
const isNewRelationEnabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IsNewRelationEnabled,
|
||||
context.workspaceId,
|
||||
);
|
||||
|
||||
const workspaceMigrationRepository = manager.getRepository(
|
||||
WorkspaceMigrationEntity,
|
||||
);
|
||||
|
||||
// Retrieve feature flags
|
||||
const workspaceFeatureFlagsMap =
|
||||
await this.featureFlagService.getWorkspaceFeatureFlagsMap(
|
||||
context.workspaceId,
|
||||
);
|
||||
|
||||
this.logger.log('Syncing standard objects and fields metadata');
|
||||
|
||||
// 1 - Sync standard objects
|
||||
@ -87,7 +90,6 @@ export class WorkspaceSyncMetadataService {
|
||||
context,
|
||||
manager,
|
||||
storage,
|
||||
workspaceFeatureFlagsMap,
|
||||
);
|
||||
|
||||
const workspaceObjectMigrationsEnd = performance.now();
|
||||
@ -103,7 +105,6 @@ export class WorkspaceSyncMetadataService {
|
||||
context,
|
||||
manager,
|
||||
storage,
|
||||
workspaceFeatureFlagsMap,
|
||||
);
|
||||
|
||||
const workspaceFieldMigrationsEnd = performance.now();
|
||||
@ -123,13 +124,24 @@ export class WorkspaceSyncMetadataService {
|
||||
|
||||
// 3 - Sync standard relations on standard and custom objects
|
||||
const workspaceRelationMigrationsStart = performance.now();
|
||||
const workspaceRelationMigrations =
|
||||
await this.workspaceSyncRelationMetadataService.synchronize(
|
||||
context,
|
||||
manager,
|
||||
storage,
|
||||
workspaceFeatureFlagsMap,
|
||||
);
|
||||
|
||||
let workspaceRelationMigrations: Partial<WorkspaceMigrationEntity>[] = [];
|
||||
|
||||
if (isNewRelationEnabled) {
|
||||
workspaceRelationMigrations =
|
||||
await this.workspaceSyncFieldMetadataRelationService.synchronize(
|
||||
context,
|
||||
manager,
|
||||
storage,
|
||||
);
|
||||
} else {
|
||||
workspaceRelationMigrations =
|
||||
await this.workspaceSyncRelationMetadataService.synchronize(
|
||||
context,
|
||||
manager,
|
||||
storage,
|
||||
);
|
||||
}
|
||||
|
||||
const workspaceRelationMigrationsEnd = performance.now();
|
||||
|
||||
@ -144,7 +156,6 @@ export class WorkspaceSyncMetadataService {
|
||||
context,
|
||||
manager,
|
||||
storage,
|
||||
workspaceFeatureFlagsMap,
|
||||
);
|
||||
|
||||
const workspaceIndexMigrationsEnd = performance.now();
|
||||
@ -160,7 +171,6 @@ export class WorkspaceSyncMetadataService {
|
||||
context,
|
||||
manager,
|
||||
storage,
|
||||
workspaceFeatureFlagsMap,
|
||||
);
|
||||
|
||||
const workspaceObjectMetadataIdentifiersEnd = performance.now();
|
||||
|
||||
Reference in New Issue
Block a user