[Extensibility] Support relation fields (#17056)
Closes https://github.com/twentyhq/core-team-issues/issues/2043
This commit is contained in:
+167
-2
@@ -6,11 +6,12 @@ import {
|
||||
ApplicationManifest,
|
||||
FieldManifest,
|
||||
ObjectManifest,
|
||||
RelationFieldManifest,
|
||||
RoleManifest,
|
||||
ServerlessFunctionManifest,
|
||||
ServerlessFunctionTriggerManifest,
|
||||
} from 'twenty-shared/application';
|
||||
import { HTTPMethod, Sources } from 'twenty-shared/types';
|
||||
import { FieldMetadataType, HTTPMethod, Sources } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
@@ -89,6 +90,12 @@ export class ApplicationSyncService {
|
||||
applicationId: application.id,
|
||||
});
|
||||
|
||||
await this.syncRelations({
|
||||
objectsToSync: manifest.objects,
|
||||
workspaceId,
|
||||
applicationId: application.id,
|
||||
});
|
||||
|
||||
if (manifest.serverlessFunctions.length > 0) {
|
||||
if (!isDefined(application.serverlessFunctionLayerId)) {
|
||||
throw new ApplicationException(
|
||||
@@ -350,6 +357,12 @@ export class ApplicationSyncService {
|
||||
}
|
||||
}
|
||||
|
||||
private isRelationFieldManifest(
|
||||
field: FieldManifest | RelationFieldManifest,
|
||||
): field is RelationFieldManifest {
|
||||
return field.type === FieldMetadataType.RELATION;
|
||||
}
|
||||
|
||||
private async syncFields({
|
||||
objectId,
|
||||
fieldsToSync,
|
||||
@@ -359,12 +372,39 @@ export class ApplicationSyncService {
|
||||
objectId: string;
|
||||
workspaceId: string;
|
||||
applicationId: string;
|
||||
fieldsToSync?: FieldManifest[];
|
||||
fieldsToSync?: (FieldManifest | RelationFieldManifest)[];
|
||||
}) {
|
||||
if (!isDefined(fieldsToSync)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const regularFields = fieldsToSync.filter(
|
||||
(field): field is FieldManifest => !this.isRelationFieldManifest(field),
|
||||
);
|
||||
|
||||
await this.syncRegularFields({
|
||||
objectId,
|
||||
fieldsToSync: regularFields,
|
||||
workspaceId,
|
||||
applicationId,
|
||||
});
|
||||
}
|
||||
|
||||
private async syncRegularFields({
|
||||
objectId,
|
||||
fieldsToSync,
|
||||
workspaceId,
|
||||
applicationId,
|
||||
}: {
|
||||
objectId: string;
|
||||
workspaceId: string;
|
||||
applicationId: string;
|
||||
fieldsToSync: FieldManifest[];
|
||||
}) {
|
||||
if (fieldsToSync.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { flatFieldMetadataMaps: existingFlatFieldMetadataMaps } =
|
||||
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
@@ -476,6 +516,82 @@ export class ApplicationSyncService {
|
||||
}
|
||||
}
|
||||
|
||||
private async syncRelationFields({
|
||||
objectId,
|
||||
relationsToSync,
|
||||
workspaceId,
|
||||
applicationId,
|
||||
flatObjectMetadataMaps,
|
||||
}: {
|
||||
objectId: string;
|
||||
relationsToSync: RelationFieldManifest[];
|
||||
workspaceId: string;
|
||||
applicationId: string;
|
||||
flatObjectMetadataMaps: {
|
||||
idByUniversalIdentifier: Partial<Record<string, string>>;
|
||||
};
|
||||
}) {
|
||||
const { flatFieldMetadataMaps } =
|
||||
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatFieldMetadataMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
for (const relation of relationsToSync) {
|
||||
const existingRelationField = Object.values(
|
||||
flatFieldMetadataMaps.byId,
|
||||
).find(
|
||||
(field) =>
|
||||
isDefined(field) &&
|
||||
field.universalIdentifier === relation.universalIdentifier,
|
||||
);
|
||||
|
||||
if (isDefined(existingRelationField)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const targetObjectId =
|
||||
flatObjectMetadataMaps.idByUniversalIdentifier[
|
||||
relation.targetObjectUniversalIdentifier
|
||||
];
|
||||
|
||||
if (!isDefined(targetObjectId)) {
|
||||
throw new ApplicationException(
|
||||
`Failed to find target object with universalIdentifier ${relation.targetObjectUniversalIdentifier}`,
|
||||
ApplicationExceptionCode.OBJECT_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const createFieldInput: CreateFieldInput = {
|
||||
name: computeMetadataNameFromLabelOrThrow(relation.label),
|
||||
type: FieldMetadataType.RELATION,
|
||||
label: relation.label,
|
||||
description: relation.description ?? undefined,
|
||||
icon: relation.icon ?? undefined,
|
||||
objectMetadataId: objectId,
|
||||
universalIdentifier: relation.universalIdentifier,
|
||||
standardId: relation.universalIdentifier,
|
||||
applicationId,
|
||||
isCustom: true,
|
||||
workspaceId,
|
||||
relationCreationPayload: {
|
||||
type: relation.relationType,
|
||||
targetObjectMetadataId: targetObjectId,
|
||||
targetFieldLabel: relation.targetFieldLabel,
|
||||
targetFieldIcon: relation.targetFieldIcon ?? 'IconRelationOneToMany',
|
||||
},
|
||||
};
|
||||
|
||||
await this.fieldMetadataService.createOneField({
|
||||
createFieldInput,
|
||||
workspaceId,
|
||||
applicationId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async syncObjects({
|
||||
objectsToSync,
|
||||
workspaceId,
|
||||
@@ -607,6 +723,55 @@ export class ApplicationSyncService {
|
||||
}
|
||||
}
|
||||
|
||||
private async syncRelations({
|
||||
objectsToSync,
|
||||
workspaceId,
|
||||
applicationId,
|
||||
}: {
|
||||
objectsToSync: ObjectManifest[];
|
||||
workspaceId: string;
|
||||
applicationId: string;
|
||||
}) {
|
||||
const { flatObjectMetadataMaps } =
|
||||
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatObjectMetadataMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
for (const objectToSync of objectsToSync) {
|
||||
const relationFields = objectToSync.fields.filter(
|
||||
(field): field is RelationFieldManifest =>
|
||||
this.isRelationFieldManifest(field),
|
||||
);
|
||||
|
||||
if (relationFields.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const sourceObjectId =
|
||||
flatObjectMetadataMaps.idByUniversalIdentifier[
|
||||
objectToSync.universalIdentifier
|
||||
];
|
||||
|
||||
if (!isDefined(sourceObjectId)) {
|
||||
throw new ApplicationException(
|
||||
`Failed to find source object with universalIdentifier ${objectToSync.universalIdentifier}`,
|
||||
ApplicationExceptionCode.OBJECT_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
await this.syncRelationFields({
|
||||
objectId: sourceObjectId,
|
||||
relationsToSync: relationFields,
|
||||
workspaceId,
|
||||
applicationId,
|
||||
flatObjectMetadataMaps,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async syncServerlessFunctions({
|
||||
serverlessFunctionsToSync,
|
||||
code,
|
||||
|
||||
@@ -15,6 +15,7 @@ export { DEFAULT_API_URL_NAME } from './constants/DefaultApiUrlName';
|
||||
export type { FieldManifest } from './fieldManifestType';
|
||||
export type { ObjectManifest } from './objectManifestType';
|
||||
export type { PackageJson } from './packageJsonType';
|
||||
export type { RelationFieldManifest } from './relationFieldManifestType';
|
||||
export type { RoleManifest } from './roleManifestType';
|
||||
export type {
|
||||
InputJsonSchema,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { type FieldManifest } from '@/application';
|
||||
import { type RelationFieldManifest } from '@/application/relationFieldManifestType';
|
||||
import { type SyncableEntityOptions } from '@/application/syncableEntityOptionsType';
|
||||
|
||||
export type ObjectManifest = SyncableEntityOptions & {
|
||||
@@ -8,5 +9,5 @@ export type ObjectManifest = SyncableEntityOptions & {
|
||||
labelPlural: string;
|
||||
description?: string;
|
||||
icon?: string;
|
||||
fields: FieldManifest[];
|
||||
fields: (FieldManifest | RelationFieldManifest)[];
|
||||
};
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { type SyncableEntityOptions } from '@/application/syncableEntityOptionsType';
|
||||
import { type FieldMetadataType } from '@/types';
|
||||
import { type RelationOnDeleteAction } from '@/types/RelationOnDeleteAction.type';
|
||||
import { type RelationType } from '@/types/RelationType';
|
||||
|
||||
export type RelationFieldManifest = SyncableEntityOptions & {
|
||||
name: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
icon?: string;
|
||||
relationType: RelationType;
|
||||
targetObjectUniversalIdentifier: string;
|
||||
targetFieldLabel: string;
|
||||
targetFieldIcon?: string;
|
||||
onDelete?: RelationOnDeleteAction;
|
||||
type: FieldMetadataType;
|
||||
};
|
||||
Reference in New Issue
Block a user