Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add label identifier to object decorator #6227

Merged
merged 13 commits into from
Jul 19, 2024
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class FixIdentifierTypes1721057142509 implements MigrationInterface {
name = 'FixIdentifierTypes1721057142509';

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "metadata"."objectMetadata" ALTER COLUMN "labelIdentifierFieldMetadataId" TYPE uuid USING "labelIdentifierFieldMetadataId"::uuid`,
);
await queryRunner.query(
`ALTER TABLE "metadata"."objectMetadata" ALTER COLUMN "imageIdentifierFieldMetadataId" TYPE uuid USING "imageIdentifierFieldMetadataId"::uuid`,
);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "metadata"."objectMetadata" ALTER COLUMN "labelIdentifierFieldMetadataId" TYPE text USING "labelIdentifierFieldMetadataId"::text`,
);
await queryRunner.query(
`ALTER TABLE "metadata"."objectMetadata" ALTER COLUMN "imageIdentifierFieldMetadataId" TYPE text USING "imageIdentifierFieldMetadataId"::text`,
);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { RelationMetadataInterface } from './relation-metadata.interface';
import { FieldMetadataInterface } from './field-metadata.interface';
import { RelationMetadataInterface } from './relation-metadata.interface';

export interface ObjectMetadataInterface {
id: string;
Expand All @@ -18,4 +18,6 @@ export interface ObjectMetadataInterface {
isActive: boolean;
isRemote: boolean;
isAuditLogged: boolean;
labelIdentifierFieldMetadataId?: string | null;
imageIdentifierFieldMetadataId?: string | null;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ObjectType, Field, HideField } from '@nestjs/graphql';
import { Field, HideField, ObjectType } from '@nestjs/graphql';

import {
Authorize,
Expand Down Expand Up @@ -72,9 +72,9 @@ export class ObjectMetadataDTO {
@Field()
updatedAt: Date;

@Field({ nullable: true })
labelIdentifierFieldMetadataId?: string;
@Field(() => String, { nullable: true })
labelIdentifierFieldMetadataId?: string | null;

@Field({ nullable: true })
imageIdentifierFieldMetadataId?: string;
@Field(() => String, { nullable: true })
imageIdentifierFieldMetadataId?: string | null;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is needed because nestjs/graphql usually infers the type from the property bellow but since now it can be either a string or null it does not know what to do so you have to specify the return type in the Field decorator

}
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import {
Entity,
Unique,
PrimaryGeneratedColumn,
Column,
OneToMany,
CreateDateColumn,
UpdateDateColumn,
Entity,
ManyToOne,
OneToMany,
PrimaryGeneratedColumn,
Relation,
Unique,
UpdateDateColumn,
} from 'typeorm';

import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';

import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
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';

@Entity('objectMetadata')
@Unique('IndexOnNameSingularAndWorkspaceIdUnique', [
Expand Down Expand Up @@ -69,11 +69,11 @@ export class ObjectMetadataEntity implements ObjectMetadataInterface {
@Column({ default: true })
isAuditLogged: boolean;

@Column({ nullable: true })
labelIdentifierFieldMetadataId?: string;
@Column({ nullable: true, type: 'uuid' })
labelIdentifierFieldMetadataId?: string | null;

@Column({ nullable: true })
imageIdentifierFieldMetadataId?: string;
@Column({ nullable: true, type: 'uuid' })
imageIdentifierFieldMetadataId?: string | null;

@Column({ nullable: false, type: 'uuid' })
workspaceId: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { metadataArgsStorage } from 'src/engine/twenty-orm/storage/metadata-args.storage';
import { BASE_OBJECT_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
import { convertClassNameToObjectMetadataName } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/convert-class-to-object-metadata-name.util';
import { TypedReflect } from 'src/utils/typed-reflect';

Expand All @@ -9,6 +10,8 @@ interface WorkspaceEntityOptions {
labelPlural: string;
description?: string;
icon?: string;
labelIdentifierStandardId?: string;
imageIdentifierStandardId?: string;
}

export function WorkspaceEntity(
Expand Down Expand Up @@ -37,6 +40,9 @@ export function WorkspaceEntity(
labelSingular: options.labelSingular,
labelPlural: options.labelPlural,
description: options.description,
labelIdentifierStandardId:
options.labelIdentifierStandardId ?? BASE_OBJECT_STANDARD_FIELD_IDS.id,
imageIdentifierStandardId: options.imageIdentifierStandardId ?? null,
icon: options.icon,
isAuditLogged,
isSystem,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,16 @@ export interface WorkspaceEntityMetadataArgs {
* Entity gate.
*/
readonly gate?: Gate;

/**
* Label identifier.
*/

readonly labelIdentifierStandardId: string | null;

/**
* Image identifier.
*/

readonly imageIdentifierStandardId: string | null;
}
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -14,6 +14,8 @@ export type PartialWorkspaceEntity = Omit<
workspaceId: string;
dataSourceId: string;
fields: (PartialFieldMetadata | PartialComputedFieldMetadata)[];
labelIdentifierStandardId?: string | null;
Weiko marked this conversation as resolved.
Show resolved Hide resolved
imageIdentifierStandardId?: string | null;
};

export type ComputedPartialWorkspaceEntity = Omit<
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you mind tackling this in this PR? I feel it's going to get lost otherwise

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what to do with them. I feel like except for people we don't really have any use case of imageIdentifier for standard objects. Also we don't have a file/image field type yet so I wanted to wait to implement properly. Wdyt?

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.`,
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 { 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 { 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 { 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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -39,6 +40,7 @@ import { WorkspaceSyncMetadataService } from 'src/engine/workspace-manager/works
...workspaceSyncMetadataComparators,
WorkspaceMetadataUpdaterService,
WorkspaceSyncObjectMetadataService,
WorkspaceSyncObjectMetadataIdentifiersService,
WorkspaceSyncRelationMetadataService,
WorkspaceSyncFieldMetadataService,
WorkspaceSyncMetadataService,
Expand Down
Loading
Loading