Remove relations for remotes (#5455)

For remotes, we will only create the foreign key, without the relation
metadata. Expected behavior will be:
- possible to create an activity. But the remote object will not be
displayed in the relations of the activity
- the remote objects should not be available in the search for relations

Also switched the number settings to an enum, since we now have to
handle `BigInt` case.

---------

Co-authored-by: Thomas Trompette <thomast@twenty.com>
This commit is contained in:
Thomas Trompette 2024-05-20 16:37:35 +02:00 committed by GitHub
parent b098027174
commit 4d479ee8ea
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 461 additions and 486 deletions

View File

@ -95,6 +95,7 @@
"graphql-fields": "^2.0.3",
"graphql-middleware": "^6.1.35",
"graphql-rate-limit": "^3.3.0",
"graphql-scalars": "^1.23.0",
"graphql-subscriptions": "2.0.0",
"graphql-tag": "^2.12.6",
"graphql-type-json": "^0.3.2",

View File

@ -29,19 +29,19 @@ export const useMultiObjectSearchMatchesSearchFilterAndToSelectQuery = ({
}) => {
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
const nonSystemObjectMetadataItems = objectMetadataItems.filter(
({ isSystem }) => !isSystem,
const selectableObjectMetadataItems = objectMetadataItems.filter(
({ isSystem, isRemote }) => !isSystem && !isRemote,
);
const { searchFilterPerMetadataItemNameSingular } =
useSearchFilterPerMetadataItem({
objectMetadataItems: nonSystemObjectMetadataItems,
objectMetadataItems: selectableObjectMetadataItems,
searchFilterValue,
});
const objectRecordsToSelectAndMatchesSearchFilterTextFilterPerMetadataItem =
Object.fromEntries(
nonSystemObjectMetadataItems
selectableObjectMetadataItems
.map(({ nameSingular }) => {
const selectedIds = selectedObjectRecordIds
.filter(
@ -74,16 +74,16 @@ export const useMultiObjectSearchMatchesSearchFilterAndToSelectQuery = ({
);
const { orderByFieldPerMetadataItem } = useOrderByFieldPerMetadataItem({
objectMetadataItems: nonSystemObjectMetadataItems,
objectMetadataItems: selectableObjectMetadataItems,
});
const { limitPerMetadataItem } = useLimitPerMetadataItem({
objectMetadataItems: nonSystemObjectMetadataItems,
objectMetadataItems: selectableObjectMetadataItems,
limit,
});
const multiSelectQuery = useGenerateCombinedFindManyRecordsQuery({
operationSignatures: nonSystemObjectMetadataItems.map(
operationSignatures: selectableObjectMetadataItems.map(
(objectMetadataItem) => ({
objectNameSingular: objectMetadataItem.nameSingular,
variables: {},

View File

@ -1,22 +1,18 @@
import {
GraphQLInputObjectType,
GraphQLList,
GraphQLNonNull,
GraphQLInt,
} from 'graphql';
import { GraphQLInputObjectType, GraphQLList, GraphQLNonNull } from 'graphql';
import { GraphQLBigInt } from 'graphql-scalars';
import { FilterIs } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input/filter-is.input-type';
export const BigIntFilterType = new GraphQLInputObjectType({
name: 'BigIntFilter',
fields: {
eq: { type: GraphQLInt },
gt: { type: GraphQLInt },
gte: { type: GraphQLInt },
in: { type: new GraphQLList(new GraphQLNonNull(GraphQLInt)) },
lt: { type: GraphQLInt },
lte: { type: GraphQLInt },
neq: { type: GraphQLInt },
eq: { type: GraphQLBigInt },
gt: { type: GraphQLBigInt },
gte: { type: GraphQLBigInt },
in: { type: new GraphQLList(new GraphQLNonNull(GraphQLBigInt)) },
lt: { type: GraphQLBigInt },
lte: { type: GraphQLBigInt },
neq: { type: GraphQLBigInt },
is: { type: FilterIs },
},
});

View File

@ -8,7 +8,6 @@ import {
GraphQLID,
GraphQLInputObjectType,
GraphQLInputType,
GraphQLInt,
GraphQLList,
GraphQLNonNull,
GraphQLScalarType,
@ -26,7 +25,6 @@ import {
BooleanFilterType,
BigFloatFilterType,
RawJsonFilterType,
IntFilterType,
} from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input';
import { OrderByDirectionType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/enum';
import {
@ -36,6 +34,8 @@ import {
import { PositionScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars/position.scalar';
import { RawJSONScalar } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars/raw-json.scalar';
import { IDFilterType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input/id-filter.input-type';
import { getNumberFilterType } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-number-filter-type.util';
import { getNumberScalarType } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-number-scalar-type.util';
export interface TypeOptions<T = any> {
nullable?: boolean;
@ -57,13 +57,6 @@ export class TypeMapperService {
return GraphQLID;
}
const numberScalar =
fieldMetadataType === FieldMetadataType.NUMBER &&
(settings as FieldMetadataSettings<FieldMetadataType.NUMBER>)
?.precision === 0
? GraphQLInt
: GraphQLFloat;
const typeScalarMapping = new Map<FieldMetadataType, GraphQLScalarType>([
[FieldMetadataType.UUID, UUIDScalarType],
[FieldMetadataType.TEXT, GraphQLString],
@ -72,7 +65,13 @@ export class TypeMapperService {
[FieldMetadataType.DATE_TIME, GraphQLISODateTime],
[FieldMetadataType.DATE, GraphQLISODateTime],
[FieldMetadataType.BOOLEAN, GraphQLBoolean],
[FieldMetadataType.NUMBER, numberScalar],
[
FieldMetadataType.NUMBER,
getNumberScalarType(
(settings as FieldMetadataSettings<FieldMetadataType.NUMBER>)
?.dataType,
),
],
[FieldMetadataType.NUMERIC, BigFloatScalarType],
[FieldMetadataType.PROBABILITY, GraphQLFloat],
[FieldMetadataType.POSITION, PositionScalarType],
@ -91,13 +90,6 @@ export class TypeMapperService {
return IDFilterType;
}
const numberScalar =
fieldMetadataType === FieldMetadataType.NUMBER &&
(settings as FieldMetadataSettings<FieldMetadataType.NUMBER>)
?.precision === 0
? IntFilterType
: FloatFilterType;
const typeFilterMapping = new Map<
FieldMetadataType,
GraphQLInputObjectType | GraphQLScalarType
@ -109,7 +101,13 @@ export class TypeMapperService {
[FieldMetadataType.DATE_TIME, DateFilterType],
[FieldMetadataType.DATE, DateFilterType],
[FieldMetadataType.BOOLEAN, BooleanFilterType],
[FieldMetadataType.NUMBER, numberScalar],
[
FieldMetadataType.NUMBER,
getNumberFilterType(
(settings as FieldMetadataSettings<FieldMetadataType.NUMBER>)
?.dataType,
),
],
[FieldMetadataType.NUMERIC, BigFloatFilterType],
[FieldMetadataType.PROBABILITY, FloatFilterType],
[FieldMetadataType.POSITION, FloatFilterType],

View File

@ -0,0 +1,24 @@
import { GraphQLInputObjectType } from 'graphql';
import { NumberDataType } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
import {
BigIntFilterType,
FloatFilterType,
IntFilterType,
} from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input';
export const getNumberFilterType = (
subType: NumberDataType | undefined,
): GraphQLInputObjectType => {
switch (subType) {
case NumberDataType.FLOAT:
return FloatFilterType;
case NumberDataType.BIGINT:
return BigIntFilterType;
case NumberDataType.INT:
return IntFilterType;
default:
return FloatFilterType;
}
};

View File

@ -0,0 +1,19 @@
import { GraphQLInt, GraphQLFloat, GraphQLScalarType } from 'graphql';
import { GraphQLBigInt } from 'graphql-scalars';
import { NumberDataType } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
export const getNumberScalarType = (
dataType: NumberDataType,
): GraphQLScalarType => {
switch (dataType) {
case NumberDataType.FLOAT:
return GraphQLFloat;
case NumberDataType.BIGINT:
return GraphQLBigInt;
case NumberDataType.INT:
return GraphQLInt;
default:
return GraphQLFloat;
}
};

View File

@ -1,11 +1,17 @@
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
export enum NumberDataType {
FLOAT = 'float',
INT = 'int',
BIGINT = 'bigint',
}
type FieldMetadataDefaultSettings = {
isForeignKey?: boolean;
};
type FieldMetadataNumberSettings = {
precision: number;
dataType: NumberDataType;
};
type FieldMetadataSettingsMapping = {

View File

@ -8,12 +8,7 @@ import { InjectRepository } from '@nestjs/typeorm';
import console from 'console';
import {
DataSource,
FindManyOptions,
FindOneOptions,
Repository,
} from 'typeorm';
import { FindManyOptions, FindOneOptions, Repository } from 'typeorm';
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
import { Query, QueryOptions } from '@ptc-org/nestjs-query-core';
@ -57,7 +52,6 @@ import {
import { createWorkspaceMigrationsForCustomObjectRelations } from 'src/engine/metadata-modules/object-metadata/utils/create-migrations-for-custom-object-relations.util';
import { createWorkspaceMigrationsForRemoteObjectRelations } from 'src/engine/metadata-modules/object-metadata/utils/create-workspace-migrations-for-remote-object-relations.util';
import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
import { validateObjectMetadataInputOrThrow } from 'src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util';
import { mapUdtNameToFieldType } from 'src/engine/metadata-modules/remote-server/remote-table/utils/udt-name-mapper.util';
import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service';
@ -380,9 +374,6 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
await this.createObjectRelationsMetadataAndMigrations(
objectMetadataInput,
createdObjectMetadata,
lastDataSourceMetadata,
workspaceDataSource,
objectMetadataInput.isRemote,
);
await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations(
@ -515,10 +506,9 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
private async createObjectRelationsMetadataAndMigrations(
objectMetadataInput: CreateObjectInput,
createdObjectMetadata: ObjectMetadataEntity,
lastDataSourceMetadata: DataSourceEntity,
workspaceDataSource: DataSource | undefined,
isRemoteObject = false,
) {
const isRemoteObject = objectMetadataInput.isRemote ?? false;
const { timelineActivityObjectMetadata } =
await this.createTimelineActivityRelation(
objectMetadataInput.workspaceId,
@ -527,6 +517,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
objectMetadataInput.primaryKeyColumnType ?? 'uuid',
),
objectMetadataInput.primaryKeyFieldMetadataSettings,
isRemoteObject,
);
const { activityTargetObjectMetadata } =
@ -537,6 +528,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
objectMetadataInput.primaryKeyColumnType ?? 'uuid',
),
objectMetadataInput.primaryKeyFieldMetadataSettings,
isRemoteObject,
);
const { favoriteObjectMetadata } = await this.createFavoriteRelation(
@ -544,6 +536,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
createdObjectMetadata,
mapUdtNameToFieldType(objectMetadataInput.primaryKeyColumnType ?? 'uuid'),
objectMetadataInput.primaryKeyFieldMetadataSettings,
isRemoteObject,
);
const { attachmentObjectMetadata } = await this.createAttachmentRelation(
@ -551,6 +544,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
createdObjectMetadata,
mapUdtNameToFieldType(objectMetadataInput.primaryKeyColumnType ?? 'uuid'),
objectMetadataInput.primaryKeyFieldMetadataSettings,
isRemoteObject,
);
return this.workspaceMigrationService.createCustomMigration(
@ -563,9 +557,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
attachmentObjectMetadata,
timelineActivityObjectMetadata,
favoriteObjectMetadata,
lastDataSourceMetadata.schema,
objectMetadataInput.primaryKeyColumnType ?? 'uuid',
workspaceDataSource,
)
: createWorkspaceMigrationsForCustomObjectRelations(
createdObjectMetadata,
@ -584,6 +576,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
objectPrimaryKeyFieldSettings:
| FieldMetadataSettings<FieldMetadataType | 'default'>
| undefined,
isRemoteObject: boolean,
) {
const activityTargetObjectMetadata =
await this.objectMetadataRepository.findOneByOrFail({
@ -591,88 +584,93 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
workspaceId: workspaceId,
});
const activityTargetRelationFieldMetadata =
await this.fieldMetadataRepository.save([
// FROM
await this.fieldMetadataRepository.save(
// Foreign key
{
standardId: createForeignKeyDeterministicUuid({
objectId: createdObjectMetadata.id,
standardId: ACTIVITY_TARGET_STANDARD_FIELD_IDS.custom,
}),
objectMetadataId: activityTargetObjectMetadata.id,
workspaceId: workspaceId,
isCustom: false,
isActive: true,
type: objectPrimaryKeyType,
name: `${createdObjectMetadata.nameSingular}Id`,
label: `${createdObjectMetadata.labelSingular} ID (foreign key)`,
description: `ActivityTarget ${createdObjectMetadata.labelSingular} id foreign key`,
icon: undefined,
isNullable: true,
isSystem: true,
defaultValue: undefined,
settings: { ...objectPrimaryKeyFieldSettings, isForeignKey: true },
},
);
if (!isRemoteObject) {
const activityTargetRelationFieldMetadata =
await this.fieldMetadataRepository.save([
// FROM
{
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.activityTargets,
objectMetadataId: createdObjectMetadata.id,
workspaceId: workspaceId,
isCustom: false,
isActive: true,
type: FieldMetadataType.RELATION,
name: 'activityTargets',
label: 'Activities',
description: `Activities tied to the ${createdObjectMetadata.labelSingular}`,
icon: 'IconCheckbox',
isNullable: true,
},
// TO
{
standardId: createRelationDeterministicUuid({
objectId: createdObjectMetadata.id,
standardId: ACTIVITY_TARGET_STANDARD_FIELD_IDS.custom,
}),
objectMetadataId: activityTargetObjectMetadata.id,
workspaceId: workspaceId,
isCustom: false,
isActive: true,
type: FieldMetadataType.RELATION,
name: createdObjectMetadata.nameSingular,
label: createdObjectMetadata.labelSingular,
description: `ActivityTarget ${createdObjectMetadata.labelSingular}`,
icon: 'IconBuildingSkyscraper',
isNullable: true,
},
]);
const activityTargetRelationFieldMetadataMap =
activityTargetRelationFieldMetadata.reduce(
(acc, fieldMetadata: FieldMetadataEntity) => {
if (fieldMetadata.type === FieldMetadataType.RELATION) {
acc[fieldMetadata.objectMetadataId] = fieldMetadata;
}
return acc;
},
{},
);
await this.relationMetadataRepository.save([
{
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.activityTargets,
objectMetadataId: createdObjectMetadata.id,
workspaceId: workspaceId,
isCustom: false,
isActive: true,
type: FieldMetadataType.RELATION,
name: 'activityTargets',
label: 'Activities',
description: `Activities tied to the ${createdObjectMetadata.labelSingular}`,
icon: 'IconCheckbox',
isNullable: true,
},
// TO
{
standardId: createRelationDeterministicUuid({
objectId: createdObjectMetadata.id,
standardId: ACTIVITY_TARGET_STANDARD_FIELD_IDS.custom,
}),
objectMetadataId: activityTargetObjectMetadata.id,
workspaceId: workspaceId,
isCustom: false,
isActive: true,
type: FieldMetadataType.RELATION,
name: createdObjectMetadata.nameSingular,
label: createdObjectMetadata.labelSingular,
description: `ActivityTarget ${createdObjectMetadata.labelSingular}`,
icon: 'IconBuildingSkyscraper',
isNullable: true,
},
// Foreign key
{
standardId: createForeignKeyDeterministicUuid({
objectId: createdObjectMetadata.id,
standardId: ACTIVITY_TARGET_STANDARD_FIELD_IDS.custom,
}),
objectMetadataId: activityTargetObjectMetadata.id,
workspaceId: workspaceId,
isCustom: false,
isActive: true,
type: objectPrimaryKeyType,
name: `${createdObjectMetadata.nameSingular}Id`,
label: `${createdObjectMetadata.labelSingular} ID (foreign key)`,
description: `ActivityTarget ${createdObjectMetadata.labelSingular} id foreign key`,
icon: undefined,
isNullable: true,
isSystem: true,
defaultValue: undefined,
settings: { ...objectPrimaryKeyFieldSettings, isForeignKey: true },
relationType: RelationMetadataType.ONE_TO_MANY,
fromObjectMetadataId: createdObjectMetadata.id,
toObjectMetadataId: activityTargetObjectMetadata.id,
fromFieldMetadataId:
activityTargetRelationFieldMetadataMap[createdObjectMetadata.id].id,
toFieldMetadataId:
activityTargetRelationFieldMetadataMap[
activityTargetObjectMetadata.id
].id,
onDeleteAction: RelationOnDeleteAction.CASCADE,
},
]);
const activityTargetRelationFieldMetadataMap =
activityTargetRelationFieldMetadata.reduce(
(acc, fieldMetadata: FieldMetadataEntity) => {
if (fieldMetadata.type === FieldMetadataType.RELATION) {
acc[fieldMetadata.objectMetadataId] = fieldMetadata;
}
return acc;
},
{},
);
await this.relationMetadataRepository.save([
{
workspaceId: workspaceId,
relationType: RelationMetadataType.ONE_TO_MANY,
fromObjectMetadataId: createdObjectMetadata.id,
toObjectMetadataId: activityTargetObjectMetadata.id,
fromFieldMetadataId:
activityTargetRelationFieldMetadataMap[createdObjectMetadata.id].id,
toFieldMetadataId:
activityTargetRelationFieldMetadataMap[
activityTargetObjectMetadata.id
].id,
onDeleteAction: RelationOnDeleteAction.CASCADE,
},
]);
}
return { activityTargetObjectMetadata };
}
@ -684,6 +682,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
objectPrimaryKeyFieldSettings:
| FieldMetadataSettings<FieldMetadataType | 'default'>
| undefined,
isRemoteObject: boolean,
) {
const attachmentObjectMetadata =
await this.objectMetadataRepository.findOneByOrFail({
@ -691,86 +690,91 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
workspaceId: workspaceId,
});
const attachmentRelationFieldMetadata =
await this.fieldMetadataRepository.save([
// FROM
await this.fieldMetadataRepository.save(
// Foreign key
{
standardId: createForeignKeyDeterministicUuid({
objectId: createdObjectMetadata.id,
standardId: ATTACHMENT_STANDARD_FIELD_IDS.custom,
}),
objectMetadataId: attachmentObjectMetadata.id,
workspaceId: workspaceId,
isCustom: false,
isActive: true,
type: objectPrimaryKeyType,
name: `${createdObjectMetadata.nameSingular}Id`,
label: `${createdObjectMetadata.labelSingular} ID (foreign key)`,
description: `Attachment ${createdObjectMetadata.labelSingular} id foreign key`,
icon: undefined,
isNullable: true,
isSystem: true,
defaultValue: undefined,
settings: { ...objectPrimaryKeyFieldSettings, isForeignKey: true },
},
);
if (!isRemoteObject) {
const attachmentRelationFieldMetadata =
await this.fieldMetadataRepository.save([
// FROM
{
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.attachments,
objectMetadataId: createdObjectMetadata.id,
workspaceId: workspaceId,
isCustom: false,
isActive: true,
type: FieldMetadataType.RELATION,
name: 'attachments',
label: 'Attachments',
description: `Attachments tied to the ${createdObjectMetadata.labelSingular}`,
icon: 'IconFileImport',
isNullable: true,
},
// TO
{
standardId: createRelationDeterministicUuid({
objectId: createdObjectMetadata.id,
standardId: ATTACHMENT_STANDARD_FIELD_IDS.custom,
}),
objectMetadataId: attachmentObjectMetadata.id,
workspaceId: workspaceId,
isCustom: false,
isActive: true,
type: FieldMetadataType.RELATION,
name: createdObjectMetadata.nameSingular,
label: createdObjectMetadata.labelSingular,
description: `Attachment ${createdObjectMetadata.labelSingular}`,
icon: 'IconBuildingSkyscraper',
isNullable: true,
},
]);
const attachmentRelationFieldMetadataMap =
attachmentRelationFieldMetadata.reduce(
(acc, fieldMetadata: FieldMetadataEntity) => {
if (fieldMetadata.type === FieldMetadataType.RELATION) {
acc[fieldMetadata.objectMetadataId] = fieldMetadata;
}
return acc;
},
{},
);
await this.relationMetadataRepository.save([
{
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.attachments,
objectMetadataId: createdObjectMetadata.id,
workspaceId: workspaceId,
isCustom: false,
isActive: true,
type: FieldMetadataType.RELATION,
name: 'attachments',
label: 'Attachments',
description: `Attachments tied to the ${createdObjectMetadata.labelSingular}`,
icon: 'IconFileImport',
isNullable: true,
},
// TO
{
standardId: createRelationDeterministicUuid({
objectId: createdObjectMetadata.id,
standardId: ATTACHMENT_STANDARD_FIELD_IDS.custom,
}),
objectMetadataId: attachmentObjectMetadata.id,
workspaceId: workspaceId,
isCustom: false,
isActive: true,
type: FieldMetadataType.RELATION,
name: createdObjectMetadata.nameSingular,
label: createdObjectMetadata.labelSingular,
description: `Attachment ${createdObjectMetadata.labelSingular}`,
icon: 'IconBuildingSkyscraper',
isNullable: true,
},
// Foreign key
{
standardId: createForeignKeyDeterministicUuid({
objectId: createdObjectMetadata.id,
standardId: ATTACHMENT_STANDARD_FIELD_IDS.custom,
}),
objectMetadataId: attachmentObjectMetadata.id,
workspaceId: workspaceId,
isCustom: false,
isActive: true,
type: objectPrimaryKeyType,
name: `${createdObjectMetadata.nameSingular}Id`,
label: `${createdObjectMetadata.labelSingular} ID (foreign key)`,
description: `Attachment ${createdObjectMetadata.labelSingular} id foreign key`,
icon: undefined,
isNullable: true,
isSystem: true,
defaultValue: undefined,
settings: { ...objectPrimaryKeyFieldSettings, isForeignKey: true },
relationType: RelationMetadataType.ONE_TO_MANY,
fromObjectMetadataId: createdObjectMetadata.id,
toObjectMetadataId: attachmentObjectMetadata.id,
fromFieldMetadataId:
attachmentRelationFieldMetadataMap[createdObjectMetadata.id].id,
toFieldMetadataId:
attachmentRelationFieldMetadataMap[attachmentObjectMetadata.id].id,
onDeleteAction: RelationOnDeleteAction.CASCADE,
},
]);
const attachmentRelationFieldMetadataMap =
attachmentRelationFieldMetadata.reduce(
(acc, fieldMetadata: FieldMetadataEntity) => {
if (fieldMetadata.type === FieldMetadataType.RELATION) {
acc[fieldMetadata.objectMetadataId] = fieldMetadata;
}
return acc;
},
{},
);
await this.relationMetadataRepository.save([
{
workspaceId: workspaceId,
relationType: RelationMetadataType.ONE_TO_MANY,
fromObjectMetadataId: createdObjectMetadata.id,
toObjectMetadataId: attachmentObjectMetadata.id,
fromFieldMetadataId:
attachmentRelationFieldMetadataMap[createdObjectMetadata.id].id,
toFieldMetadataId:
attachmentRelationFieldMetadataMap[attachmentObjectMetadata.id].id,
onDeleteAction: RelationOnDeleteAction.CASCADE,
},
]);
}
return { attachmentObjectMetadata };
}
@ -782,6 +786,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
objectPrimaryKeyFieldSettings:
| FieldMetadataSettings<FieldMetadataType | 'default'>
| undefined,
isRemoteObject: boolean,
) {
const timelineActivityObjectMetadata =
await this.objectMetadataRepository.findOneByOrFail({
@ -789,88 +794,94 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
workspaceId: workspaceId,
});
const timelineActivityRelationFieldMetadata =
await this.fieldMetadataRepository.save([
// FROM
await this.fieldMetadataRepository.save(
// Foreign key
{
standardId: createForeignKeyDeterministicUuid({
objectId: createdObjectMetadata.id,
standardId: TIMELINE_ACTIVITY_STANDARD_FIELD_IDS.custom,
}),
objectMetadataId: timelineActivityObjectMetadata.id,
workspaceId: workspaceId,
isCustom: false,
isActive: true,
type: objectPrimaryKeyType,
name: `${createdObjectMetadata.nameSingular}Id`,
label: `${createdObjectMetadata.labelSingular} ID (foreign key)`,
description: `Timeline Activity ${createdObjectMetadata.labelSingular} id foreign key`,
icon: undefined,
isNullable: true,
isSystem: true,
defaultValue: undefined,
settings: { ...objectPrimaryKeyFieldSettings, isForeignKey: true },
},
);
if (!isRemoteObject) {
const timelineActivityRelationFieldMetadata =
await this.fieldMetadataRepository.save([
// FROM
{
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.timelineActivities,
objectMetadataId: createdObjectMetadata.id,
workspaceId: workspaceId,
isCustom: false,
isActive: true,
type: FieldMetadataType.RELATION,
name: 'timelineActivities',
label: 'Timeline Activities',
description: `Timeline Activities tied to the ${createdObjectMetadata.labelSingular}`,
icon: 'IconTimeline',
isNullable: true,
},
// TO
{
standardId: createRelationDeterministicUuid({
objectId: createdObjectMetadata.id,
standardId: TIMELINE_ACTIVITY_STANDARD_FIELD_IDS.custom,
}),
objectMetadataId: timelineActivityObjectMetadata.id,
workspaceId: workspaceId,
isCustom: false,
isActive: true,
type: FieldMetadataType.RELATION,
name: createdObjectMetadata.nameSingular,
label: createdObjectMetadata.labelSingular,
description: `Timeline Activity ${createdObjectMetadata.labelSingular}`,
icon: 'IconBuildingSkyscraper',
isNullable: true,
},
]);
const timelineActivityRelationFieldMetadataMap =
timelineActivityRelationFieldMetadata.reduce(
(acc, fieldMetadata: FieldMetadataEntity) => {
if (fieldMetadata.type === FieldMetadataType.RELATION) {
acc[fieldMetadata.objectMetadataId] = fieldMetadata;
}
return acc;
},
{},
);
await this.relationMetadataRepository.save([
{
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.timelineActivities,
objectMetadataId: createdObjectMetadata.id,
workspaceId: workspaceId,
isCustom: false,
isActive: true,
type: FieldMetadataType.RELATION,
name: 'timelineActivities',
label: 'Timeline Activities',
description: `Timeline Activities tied to the ${createdObjectMetadata.labelSingular}`,
icon: 'IconTimeline',
isNullable: true,
},
// TO
{
standardId: createRelationDeterministicUuid({
objectId: createdObjectMetadata.id,
standardId: TIMELINE_ACTIVITY_STANDARD_FIELD_IDS.custom,
}),
objectMetadataId: timelineActivityObjectMetadata.id,
workspaceId: workspaceId,
isCustom: false,
isActive: true,
type: FieldMetadataType.RELATION,
name: createdObjectMetadata.nameSingular,
label: createdObjectMetadata.labelSingular,
description: `Timeline Activity ${createdObjectMetadata.labelSingular}`,
icon: 'IconBuildingSkyscraper',
isNullable: true,
},
// Foreign key
{
standardId: createForeignKeyDeterministicUuid({
objectId: createdObjectMetadata.id,
standardId: TIMELINE_ACTIVITY_STANDARD_FIELD_IDS.custom,
}),
objectMetadataId: timelineActivityObjectMetadata.id,
workspaceId: workspaceId,
isCustom: false,
isActive: true,
type: objectPrimaryKeyType,
name: `${createdObjectMetadata.nameSingular}Id`,
label: `${createdObjectMetadata.labelSingular} ID (foreign key)`,
description: `Timeline Activity ${createdObjectMetadata.labelSingular} id foreign key`,
icon: undefined,
isNullable: true,
isSystem: true,
defaultValue: undefined,
settings: { ...objectPrimaryKeyFieldSettings, isForeignKey: true },
relationType: RelationMetadataType.ONE_TO_MANY,
fromObjectMetadataId: createdObjectMetadata.id,
toObjectMetadataId: timelineActivityObjectMetadata.id,
fromFieldMetadataId:
timelineActivityRelationFieldMetadataMap[createdObjectMetadata.id]
.id,
toFieldMetadataId:
timelineActivityRelationFieldMetadataMap[
timelineActivityObjectMetadata.id
].id,
onDeleteAction: RelationOnDeleteAction.CASCADE,
},
]);
const timelineActivityRelationFieldMetadataMap =
timelineActivityRelationFieldMetadata.reduce(
(acc, fieldMetadata: FieldMetadataEntity) => {
if (fieldMetadata.type === FieldMetadataType.RELATION) {
acc[fieldMetadata.objectMetadataId] = fieldMetadata;
}
return acc;
},
{},
);
await this.relationMetadataRepository.save([
{
workspaceId: workspaceId,
relationType: RelationMetadataType.ONE_TO_MANY,
fromObjectMetadataId: createdObjectMetadata.id,
toObjectMetadataId: timelineActivityObjectMetadata.id,
fromFieldMetadataId:
timelineActivityRelationFieldMetadataMap[createdObjectMetadata.id].id,
toFieldMetadataId:
timelineActivityRelationFieldMetadataMap[
timelineActivityObjectMetadata.id
].id,
onDeleteAction: RelationOnDeleteAction.CASCADE,
},
]);
}
return { timelineActivityObjectMetadata };
}
@ -882,6 +893,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
objectPrimaryKeyFieldSettings:
| FieldMetadataSettings<FieldMetadataType | 'default'>
| undefined,
isRemoteObject: boolean,
) {
const favoriteObjectMetadata =
await this.objectMetadataRepository.findOneByOrFail({
@ -889,87 +901,92 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
workspaceId: workspaceId,
});
const favoriteRelationFieldMetadata =
await this.fieldMetadataRepository.save([
// FROM
await this.fieldMetadataRepository.save(
// Foreign key
{
standardId: createForeignKeyDeterministicUuid({
objectId: createdObjectMetadata.id,
standardId: FAVORITE_STANDARD_FIELD_IDS.custom,
}),
objectMetadataId: favoriteObjectMetadata.id,
workspaceId: workspaceId,
isCustom: false,
isActive: true,
type: objectPrimaryKeyType,
name: `${createdObjectMetadata.nameSingular}Id`,
label: `${createdObjectMetadata.labelSingular} ID (foreign key)`,
description: `Favorite ${createdObjectMetadata.labelSingular} id foreign key`,
icon: undefined,
isNullable: true,
isSystem: true,
defaultValue: undefined,
settings: { ...objectPrimaryKeyFieldSettings, isForeignKey: true },
},
);
if (!isRemoteObject) {
const favoriteRelationFieldMetadata =
await this.fieldMetadataRepository.save([
// FROM
{
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.favorites,
objectMetadataId: createdObjectMetadata.id,
workspaceId: workspaceId,
isCustom: false,
isActive: true,
isSystem: true,
type: FieldMetadataType.RELATION,
name: 'favorites',
label: 'Favorites',
description: `Favorites tied to the ${createdObjectMetadata.labelSingular}`,
icon: 'IconHeart',
isNullable: true,
},
// TO
{
standardId: createRelationDeterministicUuid({
objectId: createdObjectMetadata.id,
standardId: FAVORITE_STANDARD_FIELD_IDS.custom,
}),
objectMetadataId: favoriteObjectMetadata.id,
workspaceId: workspaceId,
isCustom: false,
isActive: true,
type: FieldMetadataType.RELATION,
name: createdObjectMetadata.nameSingular,
label: createdObjectMetadata.labelSingular,
description: `Favorite ${createdObjectMetadata.labelSingular}`,
icon: 'IconBuildingSkyscraper',
isNullable: true,
},
]);
const favoriteRelationFieldMetadataMap =
favoriteRelationFieldMetadata.reduce(
(acc, fieldMetadata: FieldMetadataEntity) => {
if (fieldMetadata.type === FieldMetadataType.RELATION) {
acc[fieldMetadata.objectMetadataId] = fieldMetadata;
}
return acc;
},
{},
);
await this.relationMetadataRepository.save([
{
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.favorites,
objectMetadataId: createdObjectMetadata.id,
workspaceId: workspaceId,
isCustom: false,
isActive: true,
isSystem: true,
type: FieldMetadataType.RELATION,
name: 'favorites',
label: 'Favorites',
description: `Favorites tied to the ${createdObjectMetadata.labelSingular}`,
icon: 'IconHeart',
isNullable: true,
},
// TO
{
standardId: createRelationDeterministicUuid({
objectId: createdObjectMetadata.id,
standardId: FAVORITE_STANDARD_FIELD_IDS.custom,
}),
objectMetadataId: favoriteObjectMetadata.id,
workspaceId: workspaceId,
isCustom: false,
isActive: true,
type: FieldMetadataType.RELATION,
name: createdObjectMetadata.nameSingular,
label: createdObjectMetadata.labelSingular,
description: `Favorite ${createdObjectMetadata.labelSingular}`,
icon: 'IconBuildingSkyscraper',
isNullable: true,
},
// Foreign key
{
standardId: createForeignKeyDeterministicUuid({
objectId: createdObjectMetadata.id,
standardId: FAVORITE_STANDARD_FIELD_IDS.custom,
}),
objectMetadataId: favoriteObjectMetadata.id,
workspaceId: workspaceId,
isCustom: false,
isActive: true,
type: objectPrimaryKeyType,
name: `${createdObjectMetadata.nameSingular}Id`,
label: `${createdObjectMetadata.labelSingular} ID (foreign key)`,
description: `Favorite ${createdObjectMetadata.labelSingular} id foreign key`,
icon: undefined,
isNullable: true,
isSystem: true,
defaultValue: undefined,
settings: { ...objectPrimaryKeyFieldSettings, isForeignKey: true },
relationType: RelationMetadataType.ONE_TO_MANY,
fromObjectMetadataId: createdObjectMetadata.id,
toObjectMetadataId: favoriteObjectMetadata.id,
fromFieldMetadataId:
favoriteRelationFieldMetadataMap[createdObjectMetadata.id].id,
toFieldMetadataId:
favoriteRelationFieldMetadataMap[favoriteObjectMetadata.id].id,
onDeleteAction: RelationOnDeleteAction.CASCADE,
},
]);
const favoriteRelationFieldMetadataMap =
favoriteRelationFieldMetadata.reduce(
(acc, fieldMetadata: FieldMetadataEntity) => {
if (fieldMetadata.type === FieldMetadataType.RELATION) {
acc[fieldMetadata.objectMetadataId] = fieldMetadata;
}
return acc;
},
{},
);
await this.relationMetadataRepository.save([
{
workspaceId: workspaceId,
relationType: RelationMetadataType.ONE_TO_MANY,
fromObjectMetadataId: createdObjectMetadata.id,
toObjectMetadataId: favoriteObjectMetadata.id,
fromFieldMetadataId:
favoriteRelationFieldMetadataMap[createdObjectMetadata.id].id,
toFieldMetadataId:
favoriteRelationFieldMetadataMap[favoriteObjectMetadata.id].id,
onDeleteAction: RelationOnDeleteAction.CASCADE,
},
]);
}
return { favoriteObjectMetadata };
}

View File

@ -13,7 +13,7 @@ export const createWorkspaceMigrationsForCustomObjectRelations = (
createdObjectMetadata: ObjectMetadataEntity,
activityTargetObjectMetadata: ObjectMetadataEntity,
attachmentObjectMetadata: ObjectMetadataEntity,
eventObjectMetadata: ObjectMetadataEntity,
timelineActivityObjectMetadata: ObjectMetadataEntity,
favoriteObjectMetadata: ObjectMetadataEntity,
): WorkspaceMigrationTableAction[] => [
{
@ -80,9 +80,9 @@ export const createWorkspaceMigrationsForCustomObjectRelations = (
},
],
},
// Add event relation
// Add timeline activity relation
{
name: computeObjectTargetTable(eventObjectMetadata),
name: computeObjectTargetTable(timelineActivityObjectMetadata),
action: WorkspaceMigrationTableActionType.ALTER,
columns: [
{
@ -96,7 +96,7 @@ export const createWorkspaceMigrationsForCustomObjectRelations = (
],
},
{
name: computeObjectTargetTable(eventObjectMetadata),
name: computeObjectTargetTable(timelineActivityObjectMetadata),
action: WorkspaceMigrationTableActionType.ALTER,
columns: [
{

View File

@ -1,5 +1,3 @@
import { DataSource } from 'typeorm';
import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import {
@ -10,55 +8,14 @@ import {
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
const buildCommentForRemoteObjectForeignKey = async (
localObjectMetadataName: string,
remoteObjectMetadataName: string,
schema: string,
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 createWorkspaceMigrationsForRemoteObjectRelations = async (
createdObjectMetadata: ObjectMetadataEntity,
activityTargetObjectMetadata: ObjectMetadataEntity,
attachmentObjectMetadata: ObjectMetadataEntity,
eventObjectMetadata: ObjectMetadataEntity,
timelineActivityObjectMetadata: ObjectMetadataEntity,
favoriteObjectMetadata: ObjectMetadataEntity,
schema: string,
primaryKeyColumnType: string,
workspaceDataSource: DataSource | undefined,
): Promise<WorkspaceMigrationTableAction[]> => {
const createdObjectName = createdObjectMetadata.nameSingular;
return [
{
name: computeObjectTargetTable(activityTargetObjectMetadata),
@ -74,22 +31,6 @@ export const createWorkspaceMigrationsForRemoteObjectRelations = async (
} satisfies WorkspaceMigrationColumnCreate,
],
},
{
name: computeObjectTargetTable(activityTargetObjectMetadata),
action: WorkspaceMigrationTableActionType.ALTER,
columns: [
{
action: WorkspaceMigrationColumnActionType.CREATE_COMMENT,
comment: await buildCommentForRemoteObjectForeignKey(
activityTargetObjectMetadata.nameSingular,
createdObjectName,
schema,
workspaceDataSource,
),
},
],
},
// Add attachment relation
{
name: computeObjectTargetTable(attachmentObjectMetadata),
action: WorkspaceMigrationTableActionType.ALTER,
@ -105,53 +46,7 @@ export const createWorkspaceMigrationsForRemoteObjectRelations = async (
],
},
{
name: computeObjectTargetTable(attachmentObjectMetadata),
action: WorkspaceMigrationTableActionType.ALTER,
columns: [
{
action: WorkspaceMigrationColumnActionType.CREATE_COMMENT,
comment: await buildCommentForRemoteObjectForeignKey(
attachmentObjectMetadata.nameSingular,
createdObjectName,
schema,
workspaceDataSource,
),
},
],
},
// Add event relation
{
name: computeObjectTargetTable(eventObjectMetadata),
action: WorkspaceMigrationTableActionType.ALTER,
columns: [
{
action: WorkspaceMigrationColumnActionType.CREATE,
columnName: computeColumnName(createdObjectMetadata.nameSingular, {
isForeignKey: true,
}),
columnType: primaryKeyColumnType,
isNullable: true,
} satisfies WorkspaceMigrationColumnCreate,
],
},
{
name: computeObjectTargetTable(eventObjectMetadata),
action: WorkspaceMigrationTableActionType.ALTER,
columns: [
{
action: WorkspaceMigrationColumnActionType.CREATE_COMMENT,
comment: await buildCommentForRemoteObjectForeignKey(
eventObjectMetadata.nameSingular,
createdObjectName,
schema,
workspaceDataSource,
),
},
],
},
// Add favorite relation
{
name: computeObjectTargetTable(favoriteObjectMetadata),
name: computeObjectTargetTable(timelineActivityObjectMetadata),
action: WorkspaceMigrationTableActionType.ALTER,
columns: [
{
@ -169,14 +64,13 @@ export const createWorkspaceMigrationsForRemoteObjectRelations = async (
action: WorkspaceMigrationTableActionType.ALTER,
columns: [
{
action: WorkspaceMigrationColumnActionType.CREATE_COMMENT,
comment: await buildCommentForRemoteObjectForeignKey(
favoriteObjectMetadata.nameSingular,
createdObjectName,
schema,
workspaceDataSource,
),
},
action: WorkspaceMigrationColumnActionType.CREATE,
columnName: computeColumnName(createdObjectMetadata.nameSingular, {
isForeignKey: true,
}),
columnType: primaryKeyColumnType,
isNullable: true,
} satisfies WorkspaceMigrationColumnCreate,
],
},
];

View File

@ -1,4 +1,7 @@
import { FieldMetadataSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
import {
FieldMetadataSettings,
NumberDataType,
} from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
@ -8,8 +11,6 @@ export const mapUdtNameToFieldType = (udtName: string): FieldMetadataType => {
return FieldMetadataType.UUID;
case 'varchar':
case 'text':
case 'bigint':
case 'int8':
return FieldMetadataType.TEXT;
case 'bool':
return FieldMetadataType.BOOLEAN;
@ -19,6 +20,8 @@ export const mapUdtNameToFieldType = (udtName: string): FieldMetadataType => {
case 'integer':
case 'int2':
case 'int4':
case 'int8':
case 'bigint':
return FieldMetadataType.NUMBER;
default:
return FieldMetadataType.TEXT;
@ -33,7 +36,12 @@ export const mapUdtNameToFieldSettings = (
case 'int2':
case 'int4':
return {
precision: 0,
dataType: NumberDataType.INT,
} satisfies FieldMetadataSettings<FieldMetadataType.NUMBER>;
case 'int8':
case 'bigint':
return {
dataType: NumberDataType.BIGINT,
} satisfies FieldMetadataSettings<FieldMetadataType.NUMBER>;
default:
return undefined;

View File

@ -28910,6 +28910,17 @@ __metadata:
languageName: node
linkType: hard
"graphql-scalars@npm:^1.23.0":
version: 1.23.0
resolution: "graphql-scalars@npm:1.23.0"
dependencies:
tslib: "npm:^2.5.0"
peerDependencies:
graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
checksum: 7666c305b8367528c4fbb583a2731d93d52f36043d71f4957ecc1d2db71d710d268e25535243beb1b2497db921daa94d3cfa83daaf4a3101a667f358ddbf3436
languageName: node
linkType: hard
"graphql-shield@npm:^7.5.0":
version: 7.6.5
resolution: "graphql-shield@npm:7.6.5"
@ -46585,6 +46596,7 @@ __metadata:
graphql-fields: "npm:^2.0.3"
graphql-middleware: "npm:^6.1.35"
graphql-rate-limit: "npm:^3.3.0"
graphql-scalars: "npm:^1.23.0"
graphql-subscriptions: "npm:2.0.0"
graphql-tag: "npm:^2.12.6"
graphql-type-json: "npm:^0.3.2"