Add label identifier to object decorator (#6227)
## Context LabelIdentifier and ImageIdentifier are metadata info attached to objectMetadata that are used to display a record in a more readable way. Those columns point to existing fields that are part of the object. For example, for a relation picker of a person, we will show a record using the "name" labelIdentifier and the "avatarUrl" imageIdentifier. <img width="215" alt="Screenshot 2024-07-11 at 18 45 51" src="https://github.com/twentyhq/twenty/assets/1834158/488f8294-0d7c-4209-b763-2499716ef29d"> Currently, the FE has a specific logic for company and people objects and we have a way to update this value via the API for custom objects, but the code is not flexible enough to change other standard objects. This PR updates the WorkspaceEntity API so we can now provide the labelIdentifier and imageIdentifier in the WorkspaceEntity decorator. Example: ```typescript @WorkspaceEntity({ standardId: STANDARD_OBJECT_IDS.activity, namePlural: 'activities', labelSingular: 'Activity', labelPlural: 'Activities', description: 'An activity', icon: 'IconCheckbox', labelIdentifierStandardId: ACTIVITY_STANDARD_FIELD_IDS.title, }) @WorkspaceIsSystem() export class ActivityWorkspaceEntity extends BaseWorkspaceEntity { @WorkspaceField({ standardId: ACTIVITY_STANDARD_FIELD_IDS.title, type: FieldMetadataType.TEXT, label: 'Title', description: 'Activity title', icon: 'IconNotes', }) title: string; ... ```
This commit is contained in:
@ -1,9 +1,9 @@
|
||||
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
||||
import {
|
||||
ComputedPartialFieldMetadata,
|
||||
PartialComputedFieldMetadata,
|
||||
PartialFieldMetadata,
|
||||
} from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-field-metadata.interface';
|
||||
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
||||
|
||||
export type PartialWorkspaceEntity = Omit<
|
||||
ObjectMetadataInterface,
|
||||
@ -14,6 +14,8 @@ export type PartialWorkspaceEntity = Omit<
|
||||
workspaceId: string;
|
||||
dataSourceId: string;
|
||||
fields: (PartialFieldMetadata | PartialComputedFieldMetadata)[];
|
||||
labelIdentifierStandardId?: string | null;
|
||||
imageIdentifierStandardId?: string | null;
|
||||
};
|
||||
|
||||
export type ComputedPartialWorkspaceEntity = Omit<
|
||||
|
||||
@ -0,0 +1,169 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { EntityManager, Repository } from 'typeorm';
|
||||
|
||||
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,
|
||||
FieldMetadataType,
|
||||
} from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { StandardObjectFactory } from 'src/engine/workspace-manager/workspace-sync-metadata/factories/standard-object.factory';
|
||||
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 WorkspaceSyncObjectMetadataIdentifiersService {
|
||||
private readonly logger = new Logger(
|
||||
WorkspaceSyncObjectMetadataIdentifiersService.name,
|
||||
);
|
||||
|
||||
constructor(private readonly standardObjectFactory: StandardObjectFactory) {}
|
||||
|
||||
async synchronize(
|
||||
context: WorkspaceSyncContext,
|
||||
manager: EntityManager,
|
||||
_storage: WorkspaceSyncStorage,
|
||||
workspaceFeatureFlagsMap: FeatureFlagMap,
|
||||
): Promise<void> {
|
||||
const objectMetadataRepository =
|
||||
manager.getRepository(ObjectMetadataEntity);
|
||||
|
||||
const originalObjectMetadataCollection =
|
||||
await this.getOriginalObjectMetadataCollection(
|
||||
context.workspaceId,
|
||||
objectMetadataRepository,
|
||||
);
|
||||
|
||||
const standardObjectMetadataMap = this.createStandardObjectMetadataMap(
|
||||
context,
|
||||
workspaceFeatureFlagsMap,
|
||||
);
|
||||
|
||||
await this.processObjectMetadataCollection(
|
||||
originalObjectMetadataCollection,
|
||||
standardObjectMetadataMap,
|
||||
objectMetadataRepository,
|
||||
);
|
||||
}
|
||||
|
||||
private async getOriginalObjectMetadataCollection(
|
||||
workspaceId: string,
|
||||
objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
): Promise<ObjectMetadataEntity[]> {
|
||||
return await objectMetadataRepository.find({
|
||||
where: { workspaceId, isCustom: false },
|
||||
relations: ['fields'],
|
||||
});
|
||||
}
|
||||
|
||||
private createStandardObjectMetadataMap(
|
||||
context: WorkspaceSyncContext,
|
||||
workspaceFeatureFlagsMap: FeatureFlagMap,
|
||||
): Record<string, any> {
|
||||
const standardObjectMetadataCollection = this.standardObjectFactory.create(
|
||||
standardObjectMetadataDefinitions,
|
||||
context,
|
||||
workspaceFeatureFlagsMap,
|
||||
);
|
||||
|
||||
return mapObjectMetadataByUniqueIdentifier(
|
||||
standardObjectMetadataCollection,
|
||||
);
|
||||
}
|
||||
|
||||
private async processObjectMetadataCollection(
|
||||
originalObjectMetadataCollection: ObjectMetadataEntity[],
|
||||
standardObjectMetadataMap: Record<string, any>,
|
||||
objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
): Promise<void> {
|
||||
for (const objectMetadata of originalObjectMetadataCollection) {
|
||||
const objectStandardId = objectMetadata.standardId;
|
||||
|
||||
if (!objectStandardId) {
|
||||
throw new Error(
|
||||
`Object ${objectMetadata.nameSingular} is missing standardId`,
|
||||
);
|
||||
}
|
||||
|
||||
const labelIdentifierFieldMetadata = this.findIdentifierFieldMetadata(
|
||||
objectMetadata,
|
||||
objectStandardId,
|
||||
standardObjectMetadataMap,
|
||||
'labelIdentifierStandardId',
|
||||
);
|
||||
|
||||
const imageIdentifierFieldMetadata = this.findIdentifierFieldMetadata(
|
||||
objectMetadata,
|
||||
objectStandardId,
|
||||
standardObjectMetadataMap,
|
||||
'imageIdentifierStandardId',
|
||||
);
|
||||
|
||||
this.validateFieldMetadata(
|
||||
objectMetadata,
|
||||
labelIdentifierFieldMetadata,
|
||||
imageIdentifierFieldMetadata,
|
||||
);
|
||||
|
||||
// TODO: Add image identifier field metadata
|
||||
await objectMetadataRepository.save({
|
||||
...objectMetadata,
|
||||
labelIdentifierFieldMetadataId:
|
||||
labelIdentifierFieldMetadata?.id ?? null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private findIdentifierFieldMetadata(
|
||||
objectMetadata: ObjectMetadataEntity,
|
||||
objectStandardId: string,
|
||||
standardObjectMetadataMap: Record<string, any>,
|
||||
standardIdFieldName: string,
|
||||
): FieldMetadataEntity | undefined {
|
||||
const identifierFieldMetadata = objectMetadata.fields.find(
|
||||
(field) =>
|
||||
field.standardId ===
|
||||
standardObjectMetadataMap[objectStandardId][standardIdFieldName],
|
||||
);
|
||||
|
||||
if (
|
||||
!identifierFieldMetadata &&
|
||||
standardObjectMetadataMap[objectStandardId][standardIdFieldName]
|
||||
) {
|
||||
throw new Error(
|
||||
`Identifier field for object ${objectMetadata.nameSingular} does not exist`,
|
||||
);
|
||||
}
|
||||
|
||||
return identifierFieldMetadata;
|
||||
}
|
||||
|
||||
private validateFieldMetadata(
|
||||
objectMetadata: ObjectMetadataEntity,
|
||||
labelIdentifierFieldMetadata: FieldMetadataEntity | undefined,
|
||||
imageIdentifierFieldMetadata: FieldMetadataEntity | undefined,
|
||||
): void {
|
||||
if (
|
||||
labelIdentifierFieldMetadata &&
|
||||
![
|
||||
FieldMetadataType.UUID,
|
||||
FieldMetadataType.TEXT,
|
||||
FieldMetadataType.FULL_NAME,
|
||||
].includes(labelIdentifierFieldMetadata.type)
|
||||
) {
|
||||
throw new Error(
|
||||
`Label identifier field for object ${objectMetadata.nameSingular} has invalid type ${labelIdentifierFieldMetadata.type}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (imageIdentifierFieldMetadata) {
|
||||
throw new Error(
|
||||
`Image identifier field for object ${objectMetadata.nameSingular} are not supported yet.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,20 +2,20 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { EntityManager } from 'typeorm';
|
||||
|
||||
import { WorkspaceSyncContext } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/workspace-sync-context.interface';
|
||||
import { ComparatorAction } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/comparator.interface';
|
||||
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';
|
||||
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { mapObjectMetadataByUniqueIdentifier } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/sync-metadata.util';
|
||||
import { WorkspaceMigrationEntity } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
|
||||
import { StandardObjectFactory } from 'src/engine/workspace-manager/workspace-sync-metadata/factories/standard-object.factory';
|
||||
import { WorkspaceObjectComparator } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/workspace-object.comparator';
|
||||
import { WorkspaceMetadataUpdaterService } from 'src/engine/workspace-manager/workspace-sync-metadata/services/workspace-metadata-updater.service';
|
||||
import { WorkspaceSyncStorage } from 'src/engine/workspace-manager/workspace-sync-metadata/storage/workspace-sync.storage';
|
||||
import { WorkspaceMigrationObjectFactory } from 'src/engine/workspace-manager/workspace-migration-builder/factories/workspace-migration-object.factory';
|
||||
import { WorkspaceObjectComparator } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/workspace-object.comparator';
|
||||
import { StandardObjectFactory } from 'src/engine/workspace-manager/workspace-sync-metadata/factories/standard-object.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 WorkspaceSyncObjectMetadataService {
|
||||
|
||||
@ -14,6 +14,7 @@ import { workspaceSyncMetadataFactories } from 'src/engine/workspace-manager/wor
|
||||
import { WorkspaceMetadataUpdaterService } from 'src/engine/workspace-manager/workspace-sync-metadata/services/workspace-metadata-updater.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';
|
||||
import { WorkspaceSyncObjectMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-object-metadata.service';
|
||||
import { WorkspaceSyncRelationMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-relation-metadata.service';
|
||||
import { WorkspaceSyncMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.service';
|
||||
@ -39,6 +40,7 @@ import { WorkspaceSyncMetadataService } from 'src/engine/workspace-manager/works
|
||||
...workspaceSyncMetadataComparators,
|
||||
WorkspaceMetadataUpdaterService,
|
||||
WorkspaceSyncObjectMetadataService,
|
||||
WorkspaceSyncObjectMetadataIdentifiersService,
|
||||
WorkspaceSyncRelationMetadataService,
|
||||
WorkspaceSyncFieldMetadataService,
|
||||
WorkspaceSyncMetadataService,
|
||||
|
||||
@ -5,15 +5,16 @@ import { DataSource } from 'typeorm';
|
||||
|
||||
import { WorkspaceSyncContext } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/workspace-sync-context.interface';
|
||||
|
||||
import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service';
|
||||
import { WorkspaceMigrationEntity } 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 { FeatureFlagFactory } from 'src/engine/workspace-manager/workspace-sync-metadata/factories/feature-flags.factory';
|
||||
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';
|
||||
import { WorkspaceSyncObjectMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-object-metadata.service';
|
||||
import { WorkspaceSyncRelationMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-relation-metadata.service';
|
||||
import { WorkspaceSyncFieldMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-field-metadata.service';
|
||||
import { WorkspaceSyncStorage } from 'src/engine/workspace-manager/workspace-sync-metadata/storage/workspace-sync.storage';
|
||||
import { WorkspaceMigrationEntity } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
|
||||
import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service';
|
||||
import { WorkspaceSyncIndexMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-index-metadata.service';
|
||||
|
||||
interface SynchronizeOptions {
|
||||
applyChanges?: boolean;
|
||||
@ -33,6 +34,7 @@ export class WorkspaceSyncMetadataService {
|
||||
private readonly workspaceSyncFieldMetadataService: WorkspaceSyncFieldMetadataService,
|
||||
private readonly workspaceCacheVersionService: WorkspaceCacheVersionService,
|
||||
private readonly workspaceSyncIndexMetadataService: WorkspaceSyncIndexMetadataService,
|
||||
private readonly workspaceSyncObjectMetadataIdentifiersService: WorkspaceSyncObjectMetadataIdentifiersService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@ -108,6 +110,14 @@ export class WorkspaceSyncMetadataService {
|
||||
workspaceFeatureFlagsMap,
|
||||
);
|
||||
|
||||
// 5 - Sync standard object metadata identifiers, does not need to return nor apply migrations
|
||||
await this.workspaceSyncObjectMetadataIdentifiersService.synchronize(
|
||||
context,
|
||||
manager,
|
||||
storage,
|
||||
workspaceFeatureFlagsMap,
|
||||
);
|
||||
|
||||
// Save workspace migrations into the database
|
||||
workspaceMigrations = await workspaceMigrationRepository.save([
|
||||
...workspaceObjectMigrations,
|
||||
@ -137,7 +147,7 @@ export class WorkspaceSyncMetadataService {
|
||||
context.workspaceId,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Sync of standard objects failed with:', error);
|
||||
this.logger.error('Sync of standard objects failed with:', error);
|
||||
await queryRunner.rollbackTransaction();
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
|
||||
Reference in New Issue
Block a user