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

Remote objects: Fix comment override - id typing - label #4784

Merged
merged 3 commits into from
Apr 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,8 @@ export class CreateObjectInput {
@IsOptional()
@Field({ nullable: true })
isRemote?: boolean;

@IsOptional()
@Field({ nullable: true })
remoteTablePrimaryKeyColumnType?: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ 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';
import { buildWorkspaceMigrationsForRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/build-workspace-migrations-for-remote-object';
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 { ObjectMetadataEntity } from './object-metadata.entity';

Expand Down Expand Up @@ -356,6 +356,14 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
createdObjectMetadata,
);

const dataSourceMetadata =
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
createdObjectMetadata.workspaceId,
);

const workspaceDataSource =
await this.typeORMService.connectToDataSource(dataSourceMetadata);

await this.workspaceMigrationService.createCustomMigration(
generateMigrationName(`create-${createdObjectMetadata.nameSingular}`),
createdObjectMetadata.workspaceId,
Expand All @@ -367,28 +375,22 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
eventObjectMetadata,
favoriteObjectMetadata,
)
: buildWorkspaceMigrationsForRemoteObject(
: await buildWorkspaceMigrationsForRemoteObject(
createdObjectMetadata,
activityTargetObjectMetadata,
attachmentObjectMetadata,
eventObjectMetadata,
favoriteObjectMetadata,
lastDataSourceMetadata.schema,
objectMetadataInput.remoteTablePrimaryKeyColumnType ?? 'uuid',
workspaceDataSource,
),
);

await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations(
createdObjectMetadata.workspaceId,
);

const dataSourceMetadata =
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
createdObjectMetadata.workspaceId,
);

const workspaceDataSource =
await this.typeORMService.connectToDataSource(dataSourceMetadata);

const view = await workspaceDataSource?.query(
`INSERT INTO ${dataSourceMetadata.schema}."view"
("objectMetadataId", "type", "name", "key", "icon")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { DataSource } from 'typeorm';

import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import {
WorkspaceMigrationTableAction,
Expand All @@ -7,21 +9,53 @@ import {
import { computeCustomName } from 'src/engine/utils/compute-custom-name.util';
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';

const buildCommentForRemoteObjectForeignKey = (
const buildCommentForRemoteObjectForeignKey = async (
localObjectMetadataName: string,
remoteObjectMetadataName: string,
schema: string,
): string =>
`@graphql({"totalCount":{"enabled": true},"foreign_keys":[{"local_name":"${localObjectMetadataName}Collection","local_columns":["${remoteObjectMetadataName}Id"],"foreign_name":"${remoteObjectMetadataName}","foreign_schema":"${schema}","foreign_table":"${remoteObjectMetadataName}","foreign_columns":["id"]}]})`;
workspaceDataSource: DataSource | undefined,
): Promise<string> => {
const existingComment = await workspaceDataSource?.query(
`SELECT col_description('${schema}."${localObjectMetadataName}"'::regclass, 0)`,
);

if (!existingComment[0]?.col_description) {
return `@graphql({"totalCount":{"enabled": true},"foreign_keys":[{"local_name":"${localObjectMetadataName}Collection","local_columns":["${remoteObjectMetadataName}Id"],"foreign_name":"${remoteObjectMetadataName}","foreign_schema":"${schema}","foreign_table":"${remoteObjectMetadataName}","foreign_columns":["id"]}]})`;
}

const commentWithoutGraphQL = existingComment[0].col_description
.replace('@graphql(', '')
.replace(')', '');
const parsedComment = JSON.parse(commentWithoutGraphQL);

const foreignKey = {
local_name: `${localObjectMetadataName}Collection`,
local_columns: [`${remoteObjectMetadataName}Id`],
foreign_name: `${remoteObjectMetadataName}`,
foreign_schema: schema,
foreign_table: remoteObjectMetadataName,
foreign_columns: ['id'],
};

if (parsedComment.foreign_keys) {
parsedComment.foreign_keys.push(foreignKey);
} else {
parsedComment.foreign_keys = [foreignKey];
}

return `@graphql(${JSON.stringify(parsedComment)})`;
};

export const buildWorkspaceMigrationsForRemoteObject = (
export const buildWorkspaceMigrationsForRemoteObject = async (
createdObjectMetadata: ObjectMetadataEntity,
activityTargetObjectMetadata: ObjectMetadataEntity,
attachmentObjectMetadata: ObjectMetadataEntity,
eventObjectMetadata: ObjectMetadataEntity,
favoriteObjectMetadata: ObjectMetadataEntity,
schema: string,
): WorkspaceMigrationTableAction[] => {
remoteTablePrimaryKeyColumnType: string,
workspaceDataSource: DataSource | undefined,
): Promise<WorkspaceMigrationTableAction[]> => {
const createdObjectName = createdObjectMetadata.nameSingular;

return [
Expand All @@ -35,7 +69,7 @@ export const buildWorkspaceMigrationsForRemoteObject = (
createdObjectMetadata.nameSingular,
false,
)}Id`,
columnType: 'uuid',
columnType: remoteTablePrimaryKeyColumnType,
isNullable: true,
} satisfies WorkspaceMigrationColumnCreate,
],
Expand All @@ -50,7 +84,7 @@ export const buildWorkspaceMigrationsForRemoteObject = (
createdObjectMetadata.nameSingular,
false,
)}Id`,
columnType: 'uuid',
columnType: remoteTablePrimaryKeyColumnType,
},
],
},
Expand All @@ -60,10 +94,11 @@ export const buildWorkspaceMigrationsForRemoteObject = (
columns: [
{
action: WorkspaceMigrationColumnActionType.CREATE_COMMENT,
comment: buildCommentForRemoteObjectForeignKey(
comment: await buildCommentForRemoteObjectForeignKey(
activityTargetObjectMetadata.nameSingular,
createdObjectName,
schema,
workspaceDataSource,
),
},
],
Expand All @@ -79,7 +114,7 @@ export const buildWorkspaceMigrationsForRemoteObject = (
createdObjectMetadata.nameSingular,
false,
)}Id`,
columnType: 'uuid',
columnType: remoteTablePrimaryKeyColumnType,
isNullable: true,
} satisfies WorkspaceMigrationColumnCreate,
],
Expand All @@ -94,7 +129,7 @@ export const buildWorkspaceMigrationsForRemoteObject = (
createdObjectMetadata.nameSingular,
false,
)}Id`,
columnType: 'uuid',
columnType: remoteTablePrimaryKeyColumnType,
},
],
},
Expand All @@ -104,10 +139,11 @@ export const buildWorkspaceMigrationsForRemoteObject = (
columns: [
{
action: WorkspaceMigrationColumnActionType.CREATE_COMMENT,
comment: buildCommentForRemoteObjectForeignKey(
comment: await buildCommentForRemoteObjectForeignKey(
attachmentObjectMetadata.nameSingular,
createdObjectName,
schema,
workspaceDataSource,
),
},
],
Expand All @@ -123,7 +159,7 @@ export const buildWorkspaceMigrationsForRemoteObject = (
createdObjectMetadata.nameSingular,
false,
)}Id`,
columnType: 'uuid',
columnType: remoteTablePrimaryKeyColumnType,
isNullable: true,
} satisfies WorkspaceMigrationColumnCreate,
],
Expand All @@ -138,7 +174,7 @@ export const buildWorkspaceMigrationsForRemoteObject = (
createdObjectMetadata.nameSingular,
false,
)}Id`,
columnType: 'uuid',
columnType: remoteTablePrimaryKeyColumnType,
},
],
},
Expand All @@ -148,10 +184,11 @@ export const buildWorkspaceMigrationsForRemoteObject = (
columns: [
{
action: WorkspaceMigrationColumnActionType.CREATE_COMMENT,
comment: buildCommentForRemoteObjectForeignKey(
comment: await buildCommentForRemoteObjectForeignKey(
eventObjectMetadata.nameSingular,
createdObjectName,
schema,
workspaceDataSource,
),
},
],
Expand All @@ -167,7 +204,7 @@ export const buildWorkspaceMigrationsForRemoteObject = (
createdObjectMetadata.nameSingular,
false,
)}Id`,
columnType: 'uuid',
columnType: remoteTablePrimaryKeyColumnType,
isNullable: true,
} satisfies WorkspaceMigrationColumnCreate,
],
Expand All @@ -182,7 +219,7 @@ export const buildWorkspaceMigrationsForRemoteObject = (
createdObjectMetadata.nameSingular,
false,
)}Id`,
columnType: 'uuid',
columnType: remoteTablePrimaryKeyColumnType,
},
],
},
Expand All @@ -192,10 +229,11 @@ export const buildWorkspaceMigrationsForRemoteObject = (
columns: [
{
action: WorkspaceMigrationColumnActionType.CREATE_COMMENT,
comment: buildCommentForRemoteObjectForeignKey(
comment: await buildCommentForRemoteObjectForeignKey(
favoriteObjectMetadata.nameSingular,
createdObjectName,
schema,
workspaceDataSource,
),
},
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ import { CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dto
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
import { RemotePostgresTableService } from 'src/engine/metadata-modules/remote-server/remote-table/remote-postgres-table/remote-postgres-table.service';
import { snakeCase } from 'src/utils/snake-case';
import { capitalize } from 'src/utils/capitalize';
import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service';
import { camelCase } from 'src/utils/camel-case';
import { camelToTitleCase } from 'src/utils/camel-to-title-case';

export class RemoteTableService {
constructor(
Expand Down Expand Up @@ -149,43 +149,52 @@ export class RemoteTableService {
.map((column) => `"${column.column_name}" ${column.data_type}`)
.join(', ');

const remoteTableName = `${camelCase(input.name)}Remote`;
const remoteTableLabel = camelToTitleCase(remoteTableName);

// We only support remote tables with an id column for now.
const remoteTableIdColumn = remoteTableColumns.filter(
(column) => column.column_name === 'id',
Weiko marked this conversation as resolved.
Show resolved Hide resolved
)?.[0];

if (!remoteTableIdColumn) {
throw new Error('Remote table must have an id column');
}

await workspaceDataSource.query(
`CREATE FOREIGN TABLE ${localSchema}."${input.name}Remote" (${foreignTableColumns}) SERVER "${remoteServer.foreignDataWrapperId}" OPTIONS (schema_name '${input.schema}', table_name '${input.name}')`,
`CREATE FOREIGN TABLE ${localSchema}."${remoteTableName}" (${foreignTableColumns}) SERVER "${remoteServer.foreignDataWrapperId}" OPTIONS (schema_name '${input.schema}', table_name '${input.name}')`,
);

await workspaceDataSource.query(
`COMMENT ON FOREIGN TABLE ${localSchema}."${input.name}Remote" IS e'@graphql({"primary_key_columns": ["id"], "totalCount": {"enabled": true}})'`,
`COMMENT ON FOREIGN TABLE ${localSchema}."${remoteTableName}" IS e'@graphql({"primary_key_columns": ["id"], "totalCount": {"enabled": true}})'`,
);

// Should be done in a transaction. To be discussed
const objectMetadata = await this.objectMetadataService.createOne({
nameSingular: `${input.name}Remote`,
namePlural: `${input.name}Remotes`,
labelSingular: `${capitalize(snakeCase(input.name)).replace(
/_/g,
' ',
)} remote`,
labelPlural: `${capitalize(snakeCase(input.name)).replace(
/_/g,
' ',
)} remotes`,
nameSingular: remoteTableName,
namePlural: `${remoteTableName}s`,
labelSingular: remoteTableLabel,
labelPlural: `${remoteTableLabel}s`,
description: 'Remote table',
dataSourceId: dataSourceMetadata.id,
workspaceId: workspaceId,
icon: 'IconUser',
isRemote: true,
remoteTablePrimaryKeyColumnType: remoteTableIdColumn.udt_name,
} as CreateObjectInput);

for (const column of remoteTableColumns) {
const field = await this.fieldMetadataService.createOne({
name: column.column_name,
label: capitalize(snakeCase(column.column_name)).replace(/_/g, ' '),
label: camelToTitleCase(camelCase(column.column_name)),
description: 'Field of remote',
// TODO: function should work for other types than Postgres
type: mapUdtNameToFieldType(column.udt_name),
workspaceId: workspaceId,
objectMetadataId: objectMetadata.id,
isRemoteCreation: true,
isNullable: true,
icon: 'IconUser',
} as CreateFieldInput);

if (column.column_name === 'id') {
Expand Down
6 changes: 6 additions & 0 deletions packages/twenty-server/src/utils/camel-to-title-case.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { capitalize } from 'src/utils/capitalize';

export const camelToTitleCase = (camelCaseText: string) =>
Weiko marked this conversation as resolved.
Show resolved Hide resolved
capitalize(camelCaseText)
.replace(/([A-Z])/g, ' $1')
.replace(/^./, (str) => str.toUpperCase());
Loading