Setup relations for remote objects (#5149)
New strategy: - add settings field on FieldMetadata. Contains a boolean isIdField and for numbers, a precision - if idField, the graphql scalar returned will be a GraphQL id. This will allow the app to work even for ids that are not uuid - remove globals dateScalar and numberScalar modes. These were not used - set limit as Integer - check manually in query runner mutations that we send a valid id Todo left: - remove WorkspaceBuildSchemaOptions since this is not used anymore. Will do in another PR --------- Co-authored-by: Thomas Trompette <thomast@twenty.com> Co-authored-by: Weiko <corentin@twenty.com>
This commit is contained in:
+11
-1
@@ -8,9 +8,13 @@ import {
|
||||
IsString,
|
||||
IsUUID,
|
||||
} from 'class-validator';
|
||||
import GraphQLJSON from 'graphql-type-json';
|
||||
|
||||
import { FieldMetadataSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
|
||||
|
||||
import { IsValidMetadataName } from 'src/engine/decorators/metadata/is-valid-metadata-name.decorator';
|
||||
import { BeforeCreateOneObject } from 'src/engine/metadata-modules/object-metadata/hooks/before-create-one-object.hook';
|
||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
|
||||
@InputType()
|
||||
@BeforeCreateOne(BeforeCreateOneObject)
|
||||
@@ -70,5 +74,11 @@ export class CreateObjectInput {
|
||||
|
||||
@IsOptional()
|
||||
@Field({ nullable: true })
|
||||
remoteTablePrimaryKeyColumnType?: string;
|
||||
primaryKeyColumnType?: string;
|
||||
|
||||
@IsOptional()
|
||||
@Field(() => GraphQLJSON, { nullable: true })
|
||||
primaryKeyFieldMetadataSettings?: FieldMetadataSettings<
|
||||
FieldMetadataType | 'default'
|
||||
>;
|
||||
}
|
||||
|
||||
+41
-28
@@ -16,6 +16,8 @@ import {
|
||||
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
||||
import { Query, QueryOptions } from '@ptc-org/nestjs-query-core';
|
||||
|
||||
import { FieldMetadataSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
|
||||
|
||||
import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.service';
|
||||
import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service';
|
||||
import {
|
||||
@@ -54,12 +56,10 @@ import {
|
||||
import { createWorkspaceMigrationsForCustomObject } from 'src/engine/metadata-modules/object-metadata/utils/create-workspace-migrations-for-custom-object.util';
|
||||
import { createWorkspaceMigrationsForRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/create-workspace-migrations-for-remote-object.util';
|
||||
import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
||||
import {
|
||||
FeatureFlagEntity,
|
||||
FeatureFlagKeys,
|
||||
} from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
|
||||
import { validateObjectMetadataInput } from 'src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util';
|
||||
import { mapUdtNameToFieldType } from 'src/engine/metadata-modules/remote-server/remote-table/remote-postgres-table/utils/remote-postgres-table.util';
|
||||
|
||||
import { ObjectMetadataEntity } from './object-metadata.entity';
|
||||
|
||||
@@ -469,35 +469,38 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
workspaceDataSource: DataSource | undefined,
|
||||
isRemoteObject = false,
|
||||
) {
|
||||
const isRelationEnabledForRemoteObjects =
|
||||
await this.isRelationEnabledForRemoteObjects(
|
||||
objectMetadataInput.workspaceId,
|
||||
);
|
||||
|
||||
if (isRemoteObject && !isRelationEnabledForRemoteObjects) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { timelineActivityObjectMetadata } =
|
||||
await this.createTimelineActivityRelation(
|
||||
objectMetadataInput.workspaceId,
|
||||
createdObjectMetadata,
|
||||
mapUdtNameToFieldType(
|
||||
objectMetadataInput.primaryKeyColumnType ?? 'uuid',
|
||||
),
|
||||
objectMetadataInput.primaryKeyFieldMetadataSettings,
|
||||
);
|
||||
|
||||
const { activityTargetObjectMetadata } =
|
||||
await this.createActivityTargetRelation(
|
||||
objectMetadataInput.workspaceId,
|
||||
createdObjectMetadata,
|
||||
mapUdtNameToFieldType(
|
||||
objectMetadataInput.primaryKeyColumnType ?? 'uuid',
|
||||
),
|
||||
objectMetadataInput.primaryKeyFieldMetadataSettings,
|
||||
);
|
||||
|
||||
const { favoriteObjectMetadata } = await this.createFavoriteRelation(
|
||||
objectMetadataInput.workspaceId,
|
||||
createdObjectMetadata,
|
||||
mapUdtNameToFieldType(objectMetadataInput.primaryKeyColumnType ?? 'uuid'),
|
||||
objectMetadataInput.primaryKeyFieldMetadataSettings,
|
||||
);
|
||||
|
||||
const { attachmentObjectMetadata } = await this.createAttachmentRelation(
|
||||
objectMetadataInput.workspaceId,
|
||||
createdObjectMetadata,
|
||||
mapUdtNameToFieldType(objectMetadataInput.primaryKeyColumnType ?? 'uuid'),
|
||||
objectMetadataInput.primaryKeyFieldMetadataSettings,
|
||||
);
|
||||
|
||||
return this.workspaceMigrationService.createCustomMigration(
|
||||
@@ -511,7 +514,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
timelineActivityObjectMetadata,
|
||||
favoriteObjectMetadata,
|
||||
lastDataSourceMetadata.schema,
|
||||
objectMetadataInput.remoteTablePrimaryKeyColumnType ?? 'uuid',
|
||||
objectMetadataInput.primaryKeyColumnType ?? 'uuid',
|
||||
workspaceDataSource,
|
||||
)
|
||||
: createWorkspaceMigrationsForCustomObject(
|
||||
@@ -527,6 +530,10 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
private async createActivityTargetRelation(
|
||||
workspaceId: string,
|
||||
createdObjectMetadata: ObjectMetadataEntity,
|
||||
objectPrimaryKeyType: FieldMetadataType,
|
||||
objectPrimaryKeyFieldSettings:
|
||||
| FieldMetadataSettings<FieldMetadataType | 'default'>
|
||||
| undefined,
|
||||
) {
|
||||
const activityTargetObjectMetadata =
|
||||
await this.objectMetadataRepository.findOneByOrFail({
|
||||
@@ -577,7 +584,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
workspaceId: workspaceId,
|
||||
isCustom: false,
|
||||
isActive: true,
|
||||
type: FieldMetadataType.UUID,
|
||||
type: objectPrimaryKeyType,
|
||||
name: `${createdObjectMetadata.nameSingular}Id`,
|
||||
label: `${createdObjectMetadata.labelSingular} ID (foreign key)`,
|
||||
description: `ActivityTarget ${createdObjectMetadata.labelSingular} id foreign key`,
|
||||
@@ -585,6 +592,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
isNullable: true,
|
||||
isSystem: true,
|
||||
defaultValue: undefined,
|
||||
settings: { ...objectPrimaryKeyFieldSettings, isForeignKey: true },
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -622,6 +630,10 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
private async createAttachmentRelation(
|
||||
workspaceId: string,
|
||||
createdObjectMetadata: ObjectMetadataEntity,
|
||||
objectPrimaryKeyType: FieldMetadataType,
|
||||
objectPrimaryKeyFieldSettings:
|
||||
| FieldMetadataSettings<FieldMetadataType | 'default'>
|
||||
| undefined,
|
||||
) {
|
||||
const attachmentObjectMetadata =
|
||||
await this.objectMetadataRepository.findOneByOrFail({
|
||||
@@ -672,7 +684,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
workspaceId: workspaceId,
|
||||
isCustom: false,
|
||||
isActive: true,
|
||||
type: FieldMetadataType.UUID,
|
||||
type: objectPrimaryKeyType,
|
||||
name: `${createdObjectMetadata.nameSingular}Id`,
|
||||
label: `${createdObjectMetadata.labelSingular} ID (foreign key)`,
|
||||
description: `Attachment ${createdObjectMetadata.labelSingular} id foreign key`,
|
||||
@@ -680,6 +692,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
isNullable: true,
|
||||
isSystem: true,
|
||||
defaultValue: undefined,
|
||||
settings: { ...objectPrimaryKeyFieldSettings, isForeignKey: true },
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -715,6 +728,10 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
private async createTimelineActivityRelation(
|
||||
workspaceId: string,
|
||||
createdObjectMetadata: ObjectMetadataEntity,
|
||||
objectPrimaryKeyType: FieldMetadataType,
|
||||
objectPrimaryKeyFieldSettings:
|
||||
| FieldMetadataSettings<FieldMetadataType | 'default'>
|
||||
| undefined,
|
||||
) {
|
||||
const timelineActivityObjectMetadata =
|
||||
await this.objectMetadataRepository.findOneByOrFail({
|
||||
@@ -765,7 +782,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
workspaceId: workspaceId,
|
||||
isCustom: false,
|
||||
isActive: true,
|
||||
type: FieldMetadataType.UUID,
|
||||
type: objectPrimaryKeyType,
|
||||
name: `${createdObjectMetadata.nameSingular}Id`,
|
||||
label: `${createdObjectMetadata.labelSingular} ID (foreign key)`,
|
||||
description: `Timeline Activity ${createdObjectMetadata.labelSingular} id foreign key`,
|
||||
@@ -773,6 +790,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
isNullable: true,
|
||||
isSystem: true,
|
||||
defaultValue: undefined,
|
||||
settings: { ...objectPrimaryKeyFieldSettings, isForeignKey: true },
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -810,6 +828,10 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
private async createFavoriteRelation(
|
||||
workspaceId: string,
|
||||
createdObjectMetadata: ObjectMetadataEntity,
|
||||
objectPrimaryKeyType: FieldMetadataType,
|
||||
objectPrimaryKeyFieldSettings:
|
||||
| FieldMetadataSettings<FieldMetadataType | 'default'>
|
||||
| undefined,
|
||||
) {
|
||||
const favoriteObjectMetadata =
|
||||
await this.objectMetadataRepository.findOneByOrFail({
|
||||
@@ -861,7 +883,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
workspaceId: workspaceId,
|
||||
isCustom: false,
|
||||
isActive: true,
|
||||
type: FieldMetadataType.UUID,
|
||||
type: objectPrimaryKeyType,
|
||||
name: `${createdObjectMetadata.nameSingular}Id`,
|
||||
label: `${createdObjectMetadata.labelSingular} ID (foreign key)`,
|
||||
description: `Favorite ${createdObjectMetadata.labelSingular} id foreign key`,
|
||||
@@ -869,6 +891,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
isNullable: true,
|
||||
isSystem: true,
|
||||
defaultValue: undefined,
|
||||
settings: { ...objectPrimaryKeyFieldSettings, isForeignKey: true },
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -900,14 +923,4 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
|
||||
return { favoriteObjectMetadata };
|
||||
}
|
||||
|
||||
private async isRelationEnabledForRemoteObjects(workspaceId: string) {
|
||||
const featureFlag = await this.featureFlagRepository.findOneBy({
|
||||
workspaceId,
|
||||
key: FeatureFlagKeys.IsRelationForRemoteObjectsEnabled,
|
||||
value: true,
|
||||
});
|
||||
|
||||
return featureFlag && featureFlag.value;
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -1,9 +1,11 @@
|
||||
import { BadRequestException } from '@nestjs/common';
|
||||
|
||||
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
||||
|
||||
export const assertMutationNotOnRemoteObject = (
|
||||
objectMetadataItem: ObjectMetadataInterface,
|
||||
) => {
|
||||
if (objectMetadataItem.isRemote) {
|
||||
throw new Error('Remote objects are read-only');
|
||||
throw new BadRequestException('Remote objects are read-only');
|
||||
}
|
||||
};
|
||||
|
||||
+5
-5
@@ -54,7 +54,7 @@ export const createWorkspaceMigrationsForRemoteObject = async (
|
||||
eventObjectMetadata: ObjectMetadataEntity,
|
||||
favoriteObjectMetadata: ObjectMetadataEntity,
|
||||
schema: string,
|
||||
remoteTablePrimaryKeyColumnType: string,
|
||||
primaryKeyColumnType: string,
|
||||
workspaceDataSource: DataSource | undefined,
|
||||
): Promise<WorkspaceMigrationTableAction[]> => {
|
||||
const createdObjectName = createdObjectMetadata.nameSingular;
|
||||
@@ -69,7 +69,7 @@ export const createWorkspaceMigrationsForRemoteObject = async (
|
||||
columnName: computeColumnName(createdObjectMetadata.nameSingular, {
|
||||
isForeignKey: true,
|
||||
}),
|
||||
columnType: remoteTablePrimaryKeyColumnType,
|
||||
columnType: primaryKeyColumnType,
|
||||
isNullable: true,
|
||||
} satisfies WorkspaceMigrationColumnCreate,
|
||||
],
|
||||
@@ -99,7 +99,7 @@ export const createWorkspaceMigrationsForRemoteObject = async (
|
||||
columnName: computeColumnName(createdObjectMetadata.nameSingular, {
|
||||
isForeignKey: true,
|
||||
}),
|
||||
columnType: remoteTablePrimaryKeyColumnType,
|
||||
columnType: primaryKeyColumnType,
|
||||
isNullable: true,
|
||||
} satisfies WorkspaceMigrationColumnCreate,
|
||||
],
|
||||
@@ -129,7 +129,7 @@ export const createWorkspaceMigrationsForRemoteObject = async (
|
||||
columnName: computeColumnName(createdObjectMetadata.nameSingular, {
|
||||
isForeignKey: true,
|
||||
}),
|
||||
columnType: remoteTablePrimaryKeyColumnType,
|
||||
columnType: primaryKeyColumnType,
|
||||
isNullable: true,
|
||||
} satisfies WorkspaceMigrationColumnCreate,
|
||||
],
|
||||
@@ -159,7 +159,7 @@ export const createWorkspaceMigrationsForRemoteObject = async (
|
||||
columnName: computeColumnName(createdObjectMetadata.nameSingular, {
|
||||
isForeignKey: true,
|
||||
}),
|
||||
columnType: remoteTablePrimaryKeyColumnType,
|
||||
columnType: primaryKeyColumnType,
|
||||
isNullable: true,
|
||||
} satisfies WorkspaceMigrationColumnCreate,
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user