Fix remote object read-only + remove relations (#4921)

- Set `readOnly` boolean in table row context. Preventing updates and
deletion
- Show page is null for remote objects. No need for complicated design
since this is temporary?
- Relation creations are now behind a feature flag for remote objects
- Refetch objects and views after syncing objects

---------

Co-authored-by: Thomas Trompette <thomast@twenty.com>
This commit is contained in:
Thomas Trompette
2024-04-11 17:58:02 +02:00
committed by GitHub
parent fc56775c2a
commit f332213e0d
29 changed files with 275 additions and 158 deletions

View File

@ -1,4 +1,5 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import {
NestjsQueryGraphQLModule,
@ -15,6 +16,8 @@ import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
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 { ObjectMetadataResolver } from 'src/engine/metadata-modules/object-metadata/object-metadata.resolver';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
import { ObjectMetadataService } from './object-metadata.service';
import { ObjectMetadataEntity } from './object-metadata.entity';
@ -32,9 +35,11 @@ import { ObjectMetadataDTO } from './dtos/object-metadata.dto';
[ObjectMetadataEntity, FieldMetadataEntity, RelationMetadataEntity],
'metadata',
),
TypeOrmModule.forFeature([FeatureFlagEntity], 'core'),
DataSourceModule,
WorkspaceMigrationModule,
WorkspaceMigrationRunnerModule,
FeatureFlagModule,
],
services: [ObjectMetadataService],
resolvers: [

View File

@ -7,7 +7,12 @@ import { InjectRepository } from '@nestjs/typeorm';
import console from 'console';
import { FindManyOptions, FindOneOptions, Repository } from 'typeorm';
import {
DataSource,
FindManyOptions,
FindOneOptions,
Repository,
} from 'typeorm';
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
import { Query, QueryOptions } from '@ptc-org/nestjs-query-core';
@ -46,9 +51,14 @@ import {
createForeignKeyDeterministicUuid,
createRelationDeterministicUuid,
} from 'src/engine/workspace-manager/workspace-sync-metadata/utils/create-deterministic-uuid.util';
import { buildWorkspaceMigrationsForCustomObject } from 'src/engine/metadata-modules/object-metadata/utils/build-workspace-migrations-for-custom-object.util';
import { buildWorkspaceMigrationsForRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/build-workspace-migrations-for-remote-object.util';
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 { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
import { ObjectMetadataEntity } from './object-metadata.entity';
@ -70,6 +80,8 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
private readonly typeORMService: TypeORMService,
private readonly workspaceMigrationService: WorkspaceMigrationService,
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
@InjectRepository(FeatureFlagEntity, 'core')
private readonly featureFlagRepository: Repository<FeatureFlagEntity>,
) {
super(objectMetadataRepository);
}
@ -322,27 +334,6 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
[],
});
const { eventObjectMetadata } = await this.createEventRelation(
objectMetadataInput.workspaceId,
createdObjectMetadata,
);
const { activityTargetObjectMetadata } =
await this.createActivityTargetRelation(
objectMetadataInput.workspaceId,
createdObjectMetadata,
);
const { favoriteObjectMetadata } = await this.createFavoriteRelation(
objectMetadataInput.workspaceId,
createdObjectMetadata,
);
const { attachmentObjectMetadata } = await this.createAttachmentRelation(
objectMetadataInput.workspaceId,
createdObjectMetadata,
);
const dataSourceMetadata =
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
createdObjectMetadata.workspaceId,
@ -351,27 +342,12 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
const workspaceDataSource =
await this.typeORMService.connectToDataSource(dataSourceMetadata);
await this.workspaceMigrationService.createCustomMigration(
generateMigrationName(`create-${createdObjectMetadata.nameSingular}`),
createdObjectMetadata.workspaceId,
isCustom
? buildWorkspaceMigrationsForCustomObject(
createdObjectMetadata,
activityTargetObjectMetadata,
attachmentObjectMetadata,
eventObjectMetadata,
favoriteObjectMetadata,
)
: await buildWorkspaceMigrationsForRemoteObject(
createdObjectMetadata,
activityTargetObjectMetadata,
attachmentObjectMetadata,
eventObjectMetadata,
favoriteObjectMetadata,
lastDataSourceMetadata.schema,
objectMetadataInput.remoteTablePrimaryKeyColumnType ?? 'uuid',
workspaceDataSource,
),
await this.createObjectRelationsMetadataAndMigrations(
objectMetadataInput,
createdObjectMetadata,
lastDataSourceMetadata,
workspaceDataSource,
objectMetadataInput.isRemote,
);
await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations(
@ -483,6 +459,67 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
await this.objectMetadataRepository.delete({ workspaceId });
}
private async createObjectRelationsMetadataAndMigrations(
objectMetadataInput: CreateObjectInput,
createdObjectMetadata: ObjectMetadataEntity,
lastDataSourceMetadata: DataSourceEntity,
workspaceDataSource: DataSource | undefined,
isRemoteObject: boolean = false,
) {
const isRelationEnabledForRemoteObjects =
await this.isRelationEnabledForRemoteObjects(
objectMetadataInput.workspaceId,
);
if (isRemoteObject && !isRelationEnabledForRemoteObjects) {
return;
}
const { eventObjectMetadata } = await this.createEventRelation(
objectMetadataInput.workspaceId,
createdObjectMetadata,
);
const { activityTargetObjectMetadata } =
await this.createActivityTargetRelation(
objectMetadataInput.workspaceId,
createdObjectMetadata,
);
const { favoriteObjectMetadata } = await this.createFavoriteRelation(
objectMetadataInput.workspaceId,
createdObjectMetadata,
);
const { attachmentObjectMetadata } = await this.createAttachmentRelation(
objectMetadataInput.workspaceId,
createdObjectMetadata,
);
return this.workspaceMigrationService.createCustomMigration(
generateMigrationName(`create-${createdObjectMetadata.nameSingular}`),
createdObjectMetadata.workspaceId,
isRemoteObject
? await createWorkspaceMigrationsForRemoteObject(
createdObjectMetadata,
activityTargetObjectMetadata,
attachmentObjectMetadata,
eventObjectMetadata,
favoriteObjectMetadata,
lastDataSourceMetadata.schema,
objectMetadataInput.remoteTablePrimaryKeyColumnType ?? 'uuid',
workspaceDataSource,
)
: createWorkspaceMigrationsForCustomObject(
createdObjectMetadata,
activityTargetObjectMetadata,
attachmentObjectMetadata,
eventObjectMetadata,
favoriteObjectMetadata,
),
);
}
private async createActivityTargetRelation(
workspaceId: string,
createdObjectMetadata: ObjectMetadataEntity,
@ -855,4 +892,14 @@ 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;
}
}

View File

@ -9,7 +9,7 @@ import {
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
export const buildWorkspaceMigrationsForCustomObject = (
export const createWorkspaceMigrationsForCustomObject = (
createdObjectMetadata: ObjectMetadataEntity,
activityTargetObjectMetadata: ObjectMetadataEntity,
attachmentObjectMetadata: ObjectMetadataEntity,

View File

@ -47,7 +47,7 @@ const buildCommentForRemoteObjectForeignKey = async (
return `@graphql(${JSON.stringify(parsedComment)})`;
};
export const buildWorkspaceMigrationsForRemoteObject = async (
export const createWorkspaceMigrationsForRemoteObject = async (
createdObjectMetadata: ObjectMetadataEntity,
activityTargetObjectMetadata: ObjectMetadataEntity,
attachmentObjectMetadata: ObjectMetadataEntity,
@ -74,19 +74,6 @@ export const buildWorkspaceMigrationsForRemoteObject = async (
} satisfies WorkspaceMigrationColumnCreate,
],
},
{
name: computeObjectTargetTable(activityTargetObjectMetadata),
action: WorkspaceMigrationTableActionType.ALTER,
columns: [
{
action: WorkspaceMigrationColumnActionType.CREATE,
columnName: computeColumnName(createdObjectMetadata.nameSingular, {
isForeignKey: true,
}),
columnType: remoteTablePrimaryKeyColumnType,
},
],
},
{
name: computeObjectTargetTable(activityTargetObjectMetadata),
action: WorkspaceMigrationTableActionType.ALTER,
@ -117,19 +104,6 @@ export const buildWorkspaceMigrationsForRemoteObject = async (
} satisfies WorkspaceMigrationColumnCreate,
],
},
{
name: computeObjectTargetTable(attachmentObjectMetadata),
action: WorkspaceMigrationTableActionType.ALTER,
columns: [
{
action: WorkspaceMigrationColumnActionType.CREATE,
columnName: computeColumnName(createdObjectMetadata.nameSingular, {
isForeignKey: true,
}),
columnType: remoteTablePrimaryKeyColumnType,
},
],
},
{
name: computeObjectTargetTable(attachmentObjectMetadata),
action: WorkspaceMigrationTableActionType.ALTER,
@ -160,19 +134,6 @@ export const buildWorkspaceMigrationsForRemoteObject = async (
} satisfies WorkspaceMigrationColumnCreate,
],
},
{
name: computeObjectTargetTable(eventObjectMetadata),
action: WorkspaceMigrationTableActionType.ALTER,
columns: [
{
action: WorkspaceMigrationColumnActionType.CREATE,
columnName: computeColumnName(createdObjectMetadata.nameSingular, {
isForeignKey: true,
}),
columnType: remoteTablePrimaryKeyColumnType,
},
],
},
{
name: computeObjectTargetTable(eventObjectMetadata),
action: WorkspaceMigrationTableActionType.ALTER,
@ -203,19 +164,6 @@ export const buildWorkspaceMigrationsForRemoteObject = async (
} satisfies WorkspaceMigrationColumnCreate,
],
},
{
name: computeObjectTargetTable(favoriteObjectMetadata),
action: WorkspaceMigrationTableActionType.ALTER,
columns: [
{
action: WorkspaceMigrationColumnActionType.CREATE,
columnName: computeColumnName(createdObjectMetadata.nameSingular, {
isForeignKey: true,
}),
columnType: remoteTablePrimaryKeyColumnType,
},
],
},
{
name: computeObjectTargetTable(favoriteObjectMetadata),
action: WorkspaceMigrationTableActionType.ALTER,

View File

@ -197,7 +197,7 @@ export class RemoteTableService {
description: 'Remote table',
dataSourceId: dataSourceMetatada.id,
workspaceId: workspaceId,
icon: 'IconUser',
icon: 'IconPlug',
isRemote: true,
remoteTablePrimaryKeyColumnType: remoteTableIdColumn.udtName,
} satisfies CreateObjectInput);
@ -213,7 +213,7 @@ export class RemoteTableService {
objectMetadataId: objectMetadata.id,
isRemoteCreation: true,
isNullable: true,
icon: 'IconUser',
icon: 'IconPlug',
} satisfies CreateFieldInput);
if (column.columnName === 'id') {