fix: multiple twenty orm issues & show an example of use (#5439)

This PR is fixing some issues and adding enhancement in TwentyORM:

- [x] Composite fields in nested relations are not formatted properly
- [x] Passing operators like `Any` in `where` condition is breaking the
query
- [x] Ability to auto load workspace-entities based on a regex path

I've also introduced an example of use for `CalendarEventService`:


https://github.com/twentyhq/twenty/pull/5439/files#diff-3a7dffc0dea57345d10e70c648e911f98fe237248bcea124dafa9c8deb1db748R15
This commit is contained in:
Jérémy M
2024-05-20 11:01:47 +02:00
committed by GitHub
parent 81e8f49033
commit 8b5f79ddbf
147 changed files with 1108 additions and 1101 deletions

View File

@ -7,7 +7,7 @@ import {
FieldComparatorResult,
} from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/comparator.interface';
import { ComputedPartialFieldMetadata } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-field-metadata.interface';
import { ComputedPartialObjectMetadata } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-object-metadata.interface';
import { ComputedPartialWorkspaceEntity } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-object-metadata.interface';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { transformMetadataForComparison } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/utils/transform-metadata-for-comparison.util';
@ -36,7 +36,7 @@ export class WorkspaceFieldComparator {
public compare(
originalObjectMetadata: ObjectMetadataEntity,
standardObjectMetadata: ComputedPartialObjectMetadata,
standardObjectMetadata: ComputedPartialWorkspaceEntity,
): FieldComparatorResult[] {
const result: FieldComparatorResult[] = [];
const fieldPropertiesToUpdateMap: Record<

View File

@ -7,7 +7,7 @@ import {
ComparatorAction,
ObjectComparatorResult,
} from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/comparator.interface';
import { ComputedPartialObjectMetadata } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-object-metadata.interface';
import { ComputedPartialWorkspaceEntity } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-object-metadata.interface';
import { transformMetadataForComparison } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/utils/transform-metadata-for-comparison.util';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
@ -28,7 +28,7 @@ export class WorkspaceObjectComparator {
public compare(
originalObjectMetadata: ObjectMetadataEntity | undefined,
standardObjectMetadata: ComputedPartialObjectMetadata,
standardObjectMetadata: ComputedPartialWorkspaceEntity,
): ObjectComparatorResult {
// If the object doesn't exist in the original metadata, we need to create it
if (!originalObjectMetadata) {
@ -38,7 +38,8 @@ export class WorkspaceObjectComparator {
};
}
const objectPropertiesToUpdate: Partial<ComputedPartialObjectMetadata> = {};
const objectPropertiesToUpdate: Partial<ComputedPartialWorkspaceEntity> =
{};
// Only compare properties that are not ignored
const partialOriginalObjectMetadata = transformMetadataForComparison(

View File

@ -1,7 +1,7 @@
import { Injectable } from '@nestjs/common';
import { WorkspaceSyncContext } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/workspace-sync-context.interface';
import { PartialObjectMetadata } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-object-metadata.interface';
import { PartialWorkspaceEntity } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-object-metadata.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';
@ -18,19 +18,19 @@ export class StandardObjectFactory {
standardObjectMetadataDefinitions: (typeof BaseWorkspaceEntity)[],
context: WorkspaceSyncContext,
workspaceFeatureFlagsMap: FeatureFlagMap,
): PartialObjectMetadata[] {
): PartialWorkspaceEntity[] {
return standardObjectMetadataDefinitions
.map((metadata) =>
this.createObjectMetadata(metadata, context, workspaceFeatureFlagsMap),
)
.filter((metadata): metadata is PartialObjectMetadata => !!metadata);
.filter((metadata): metadata is PartialWorkspaceEntity => !!metadata);
}
private createObjectMetadata(
target: typeof BaseWorkspaceEntity,
context: WorkspaceSyncContext,
workspaceFeatureFlagsMap: FeatureFlagMap,
): PartialObjectMetadata | undefined {
): PartialWorkspaceEntity | undefined {
const workspaceEntityMetadataArgs =
metadataArgsStorage.filterEntities(target);

View File

@ -2,7 +2,7 @@ import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/
import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
import { ComputedPartialFieldMetadata } from './partial-field-metadata.interface';
import { ComputedPartialObjectMetadata } from './partial-object-metadata.interface';
import { ComputedPartialWorkspaceEntity } from './partial-object-metadata.interface';
export const enum ComparatorAction {
SKIP = 'SKIP',
@ -32,9 +32,9 @@ export interface ComparatorDeleteResult<T> {
export type ObjectComparatorResult =
| ComparatorSkipResult
| ComparatorCreateResult<ComputedPartialObjectMetadata>
| ComparatorCreateResult<ComputedPartialWorkspaceEntity>
| ComparatorUpdateResult<
Partial<ComputedPartialObjectMetadata> & { id: string }
Partial<ComputedPartialWorkspaceEntity> & { id: string }
>;
export type FieldComparatorResult =

View File

@ -1,13 +1,13 @@
import { PartialFieldMetadata } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-field-metadata.interface';
import { PartialObjectMetadata } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-object-metadata.interface';
import { PartialWorkspaceEntity } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-object-metadata.interface';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
export type MappedFieldMetadata = Record<string, PartialFieldMetadata>;
export interface MappedObjectMetadata
extends Omit<PartialObjectMetadata, 'fields'> {
export interface MappedWorkspaceEntity
extends Omit<PartialWorkspaceEntity, 'fields'> {
fields: MappedFieldMetadata;
}

View File

@ -5,7 +5,7 @@ import {
} 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 PartialObjectMetadata = Omit<
export type PartialWorkspaceEntity = Omit<
ObjectMetadataInterface,
'id' | 'standardId' | 'fromRelations' | 'toRelations' | 'fields' | 'isActive'
> & {
@ -16,8 +16,8 @@ export type PartialObjectMetadata = Omit<
fields: (PartialFieldMetadata | PartialComputedFieldMetadata)[];
};
export type ComputedPartialObjectMetadata = Omit<
PartialObjectMetadata,
export type ComputedPartialWorkspaceEntity = Omit<
PartialWorkspaceEntity,
'standardId' | 'fields'
> & {
standardId: string | null;

View File

@ -1,61 +1,61 @@
import { ActivityTargetObjectMetadata } from 'src/modules/activity/standard-objects/activity-target.object-metadata';
import { ActivityObjectMetadata } from 'src/modules/activity/standard-objects/activity.object-metadata';
import { ApiKeyObjectMetadata } from 'src/modules/api-key/standard-objects/api-key.object-metadata';
import { AttachmentObjectMetadata } from 'src/modules/attachment/standard-objects/attachment.object-metadata';
import { BlocklistObjectMetadata } from 'src/modules/connected-account/standard-objects/blocklist.object-metadata';
import { CalendarEventObjectMetadata } from 'src/modules/calendar/standard-objects/calendar-event.object-metadata';
import { CalendarChannelObjectMetadata } from 'src/modules/calendar/standard-objects/calendar-channel.object-metadata';
import { CalendarEventParticipantObjectMetadata } from 'src/modules/calendar/standard-objects/calendar-event-participant.object-metadata';
import { CommentObjectMetadata } from 'src/modules/activity/standard-objects/comment.object-metadata';
import { CompanyObjectMetadata } from 'src/modules/company/standard-objects/company.object-metadata';
import { ConnectedAccountObjectMetadata } from 'src/modules/connected-account/standard-objects/connected-account.object-metadata';
import { FavoriteObjectMetadata } from 'src/modules/favorite/standard-objects/favorite.object-metadata';
import { MessageChannelMessageAssociationObjectMetadata } from 'src/modules/messaging/standard-objects/message-channel-message-association.object-metadata';
import { MessageChannelObjectMetadata } from 'src/modules/messaging/standard-objects/message-channel.object-metadata';
import { MessageParticipantObjectMetadata } from 'src/modules/messaging/standard-objects/message-participant.object-metadata';
import { MessageThreadObjectMetadata } from 'src/modules/messaging/standard-objects/message-thread.object-metadata';
import { MessageObjectMetadata } from 'src/modules/messaging/standard-objects/message.object-metadata';
import { OpportunityObjectMetadata } from 'src/modules/opportunity/standard-objects/opportunity.object-metadata';
import { PersonObjectMetadata } from 'src/modules/person/standard-objects/person.object-metadata';
import { ViewFieldObjectMetadata } from 'src/modules/view/standard-objects/view-field.object-metadata';
import { ViewFilterObjectMetadata } from 'src/modules/view/standard-objects/view-filter.object-metadata';
import { ViewSortObjectMetadata } from 'src/modules/view/standard-objects/view-sort.object-metadata';
import { ViewObjectMetadata } from 'src/modules/view/standard-objects/view.object-metadata';
import { WebhookObjectMetadata } from 'src/modules/webhook/standard-objects/webhook.object-metadata';
import { WorkspaceMemberObjectMetadata } from 'src/modules/workspace-member/standard-objects/workspace-member.object-metadata';
import { CalendarChannelEventAssociationObjectMetadata } from 'src/modules/calendar/standard-objects/calendar-channel-event-association.object-metadata';
import { AuditLogObjectMetadata } from 'src/modules/timeline/standard-objects/audit-log.object-metadata';
import { TimelineActivityObjectMetadata } from 'src/modules/timeline/standard-objects/timeline-activity.object-metadata';
import { BehavioralEventObjectMetadata } from 'src/modules/timeline/standard-objects/behavioral-event.object-metadata';
import { ActivityTargetWorkspaceEntity } from 'src/modules/activity/standard-objects/activity-target.workspace-entity';
import { ActivityWorkspaceEntity } from 'src/modules/activity/standard-objects/activity.workspace-entity';
import { ApiKeyWorkspaceEntity } from 'src/modules/api-key/standard-objects/api-key.workspace-entity';
import { AttachmentWorkspaceEntity } from 'src/modules/attachment/standard-objects/attachment.workspace-entity';
import { BlocklistWorkspaceEntity } from 'src/modules/connected-account/standard-objects/blocklist.workspace-entity';
import { CalendarEventWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-event.workspace-entity';
import { CalendarChannelWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-channel.workspace-entity';
import { CalendarEventParticipantWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-event-participant.workspace-entity';
import { CommentWorkspaceEntity } from 'src/modules/activity/standard-objects/comment.workspace-entity';
import { CompanyWorkspaceEntity } from 'src/modules/company/standard-objects/company.workspace-entity';
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
import { FavoriteWorkspaceEntity } from 'src/modules/favorite/standard-objects/favorite.workspace-entity';
import { MessageChannelMessageAssociationWorkspaceEntity } from 'src/modules/messaging/standard-objects/message-channel-message-association.workspace-entity';
import { MessageChannelWorkspaceEntity } from 'src/modules/messaging/standard-objects/message-channel.workspace-entity';
import { MessageParticipantWorkspaceEntity } from 'src/modules/messaging/standard-objects/message-participant.workspace-entity';
import { MessageThreadWorkspaceEntity } from 'src/modules/messaging/standard-objects/message-thread.workspace-entity';
import { MessageWorkspaceEntity } from 'src/modules/messaging/standard-objects/message.workspace-entity';
import { OpportunityWorkspaceEntity } from 'src/modules/opportunity/standard-objects/opportunity.workspace-entity';
import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity';
import { ViewFieldWorkspaceEntity } from 'src/modules/view/standard-objects/view-field.workspace-entity';
import { ViewFilterWorkspaceEntity } from 'src/modules/view/standard-objects/view-filter.workspace-entity';
import { ViewSortWorkspaceEntity } from 'src/modules/view/standard-objects/view-sort.workspace-entity';
import { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity';
import { WebhookWorkspaceEntity } from 'src/modules/webhook/standard-objects/webhook.workspace-entity';
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
import { CalendarChannelEventAssociationWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-channel-event-association.workspace-entity';
import { AuditLogWorkspaceEntity } from 'src/modules/timeline/standard-objects/audit-log.workspace-entity';
import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity';
import { BehavioralEventWorkspaceEntity } from 'src/modules/timeline/standard-objects/behavioral-event.workspace-entity';
export const standardObjectMetadataDefinitions = [
ActivityTargetObjectMetadata,
ActivityObjectMetadata,
ApiKeyObjectMetadata,
AuditLogObjectMetadata,
AttachmentObjectMetadata,
BehavioralEventObjectMetadata,
BlocklistObjectMetadata,
CalendarEventObjectMetadata,
CalendarChannelObjectMetadata,
CalendarChannelEventAssociationObjectMetadata,
CalendarEventParticipantObjectMetadata,
CommentObjectMetadata,
CompanyObjectMetadata,
ConnectedAccountObjectMetadata,
FavoriteObjectMetadata,
OpportunityObjectMetadata,
PersonObjectMetadata,
TimelineActivityObjectMetadata,
ViewFieldObjectMetadata,
ViewFilterObjectMetadata,
ViewSortObjectMetadata,
ViewObjectMetadata,
WebhookObjectMetadata,
WorkspaceMemberObjectMetadata,
MessageThreadObjectMetadata,
MessageObjectMetadata,
MessageChannelObjectMetadata,
MessageParticipantObjectMetadata,
MessageChannelMessageAssociationObjectMetadata,
ActivityTargetWorkspaceEntity,
ActivityWorkspaceEntity,
ApiKeyWorkspaceEntity,
AuditLogWorkspaceEntity,
AttachmentWorkspaceEntity,
BehavioralEventWorkspaceEntity,
BlocklistWorkspaceEntity,
CalendarEventWorkspaceEntity,
CalendarChannelWorkspaceEntity,
CalendarChannelEventAssociationWorkspaceEntity,
CalendarEventParticipantWorkspaceEntity,
CommentWorkspaceEntity,
CompanyWorkspaceEntity,
ConnectedAccountWorkspaceEntity,
FavoriteWorkspaceEntity,
OpportunityWorkspaceEntity,
PersonWorkspaceEntity,
TimelineActivityWorkspaceEntity,
ViewFieldWorkspaceEntity,
ViewFilterWorkspaceEntity,
ViewSortWorkspaceEntity,
ViewWorkspaceEntity,
WebhookWorkspaceEntity,
WorkspaceMemberWorkspaceEntity,
MessageThreadWorkspaceEntity,
MessageWorkspaceEntity,
MessageChannelWorkspaceEntity,
MessageParticipantWorkspaceEntity,
MessageChannelMessageAssociationWorkspaceEntity,
];

View File

@ -1,4 +1,4 @@
import { ComputedPartialObjectMetadata } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-object-metadata.interface';
import { ComputedPartialWorkspaceEntity } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-object-metadata.interface';
import { ComputedPartialFieldMetadata } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-field-metadata.interface';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
@ -7,9 +7,9 @@ import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-met
export class WorkspaceSyncStorage {
// Object metadata
private readonly _objectMetadataCreateCollection: ComputedPartialObjectMetadata[] =
private readonly _objectMetadataCreateCollection: ComputedPartialWorkspaceEntity[] =
[];
private readonly _objectMetadataUpdateCollection: (Partial<ComputedPartialObjectMetadata> & {
private readonly _objectMetadataUpdateCollection: (Partial<ComputedPartialWorkspaceEntity> & {
id: string;
})[] = [];
private readonly _objectMetadataDeleteCollection: ObjectMetadataEntity[] = [];
@ -68,12 +68,12 @@ export class WorkspaceSyncStorage {
return this._relationMetadataDeleteCollection;
}
addCreateObjectMetadata(object: ComputedPartialObjectMetadata) {
addCreateObjectMetadata(object: ComputedPartialWorkspaceEntity) {
this._objectMetadataCreateCollection.push(object);
}
addUpdateObjectMetadata(
object: Partial<ComputedPartialObjectMetadata> & { id: string },
object: Partial<ComputedPartialWorkspaceEntity> & { id: string },
) {
this._objectMetadataUpdateCollection.push(object);
}

View File

@ -1,9 +1,19 @@
import { ObjectLiteral } from 'typeorm';
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
export type ObjectRecord<T extends BaseWorkspaceEntity> = {
export type ObjectRecord<T extends ObjectLiteral> = {
[K in keyof T as T[K] extends BaseWorkspaceEntity
? `${Extract<K, string>}Id`
: K]: T[K] extends BaseWorkspaceEntity ? string : T[K];
: K]: T[K] extends BaseWorkspaceEntity
? string
: T[K] extends BaseWorkspaceEntity[]
? string[]
: T[K];
} & {
[K in keyof T]: T[K] extends BaseWorkspaceEntity ? ObjectRecord<T[K]> : T[K];
[K in keyof T]: T[K] extends BaseWorkspaceEntity
? ObjectRecord<T[K]>
: T[K] extends BaseWorkspaceEntity[]
? ObjectRecord<T[K][number]>[]
: T[K];
};

View File

@ -1,6 +1,6 @@
import {
ComputedPartialObjectMetadata,
PartialObjectMetadata,
ComputedPartialWorkspaceEntity,
PartialWorkspaceEntity,
} from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-object-metadata.interface';
import { ComputedPartialFieldMetadata } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-field-metadata.interface';
@ -12,12 +12,12 @@ import {
} from 'src/engine/workspace-manager/workspace-sync-metadata/utils/create-deterministic-uuid.util';
export const computeStandardObject = (
standardObjectMetadata: Omit<PartialObjectMetadata, 'standardId'> & {
standardObjectMetadata: Omit<PartialWorkspaceEntity, 'standardId'> & {
standardId: string | null;
},
originalObjectMetadata: ObjectMetadataEntity,
customObjectMetadataCollection: ObjectMetadataEntity[] = [],
): ComputedPartialObjectMetadata => {
): ComputedPartialWorkspaceEntity => {
const fields: ComputedPartialFieldMetadata[] = [];
for (const partialFieldMetadata of standardObjectMetadata.fields) {

View File

@ -1,7 +1,8 @@
import { camelCase } from 'src/utils/camel-case';
const classSuffix = 'WorkspaceEntity';
export const convertClassNameToObjectMetadataName = (name: string): string => {
const classSuffix = 'ObjectMetadata';
let objectName = camelCase(name);
if (objectName.endsWith(classSuffix)) {