Connect logic in Workspace Entity Manager (#13078)

Large PR, sorry for that. Don't hesitate to reach me to have full
context (env. 500lines for integration and unit tests)

- Add connect logic in Workspace Entity Manager
- Update QueryDeepPartialEntity type to enable dev to use connect
- Add integration test on createOne / createMany
- Add unit test to cover main utils
- Remove feature flag on connect

closes https://github.com/twentyhq/core-team-issues/issues/1148
closes https://github.com/twentyhq/core-team-issues/issues/1147
This commit is contained in:
Etienne
2025-07-09 14:16:28 +02:00
committed by GitHub
parent a95ca10f29
commit fce33004bc
27 changed files with 1293 additions and 80 deletions

View File

@ -13,6 +13,8 @@ import { RecordTransformerException } from 'src/engine/core-modules/record-trans
import { recordTransformerGraphqlApiExceptionHandler } from 'src/engine/core-modules/record-transformer/utils/record-transformer-graphql-api-exception-handler.util';
import { PermissionsException } from 'src/engine/metadata-modules/permissions/permissions.exception';
import { permissionGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/permissions/utils/permission-graphql-api-exception-handler.util';
import { TwentyORMException } from 'src/engine/twenty-orm/exceptions/twenty-orm.exception';
import { twentyORMGraphqlApiExceptionHandler } from 'src/engine/twenty-orm/utils/twenty-orm-graphql-api-exception-handler.util';
interface QueryFailedErrorWithCode extends QueryFailedError {
code: string;
@ -44,6 +46,8 @@ export const workspaceQueryRunnerGraphqlApiExceptionHandler = (
return workspaceExceptionHandler(error);
case error instanceof GraphqlQueryRunnerException:
return graphqlQueryRunnerExceptionHandler(error);
case error instanceof TwentyORMException:
return twentyORMGraphqlApiExceptionHandler(error);
default:
throw error;
}

View File

@ -36,12 +36,10 @@ export class InputTypeDefinitionFactory {
objectMetadata,
kind,
options,
isRelationConnectEnabled = false,
}: {
objectMetadata: ObjectMetadataInterface;
kind: InputTypeDefinitionKind;
options: WorkspaceBuildSchemaOptions;
isRelationConnectEnabled?: boolean;
}): InputTypeDefinition {
// @ts-expect-error legacy noImplicitAny
const inputType = new GraphQLInputObjectType({
@ -89,7 +87,6 @@ export class InputTypeDefinitionFactory {
kind,
options,
typeFactory: this.inputTypeFactory,
isRelationConnectEnabled,
});
}
},

View File

@ -39,11 +39,6 @@ export class RelationConnectInputTypeDefinitionFactory {
kind: InputTypeDefinitionKind.Create,
type: fields,
},
{
target,
kind: InputTypeDefinitionKind.Update,
type: fields,
},
];
}

View File

@ -13,10 +13,4 @@ export interface WorkspaceBuildSchemaOptions {
* @default 'float'
*/
numberScalarMode?: NumberScalarMode;
/**
* Workspace ID - used to relation connect feature flag check
* TODO: remove once IS_RELATION_CONNECT_ENABLED is removed
*/
workspaceId?: string;
}

View File

@ -1,7 +1,5 @@
import { Injectable, Logger } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { CompositeType } from 'src/engine/metadata-modules/field-metadata/interfaces/composite-type.interface';
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
@ -11,7 +9,6 @@ import { CompositeObjectTypeDefinitionFactory } from 'src/engine/api/graphql/wor
import { EnumTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/enum-type-definition.factory';
import { ExtendObjectTypeDefinitionV2Factory } from 'src/engine/api/graphql/workspace-schema-builder/factories/extend-object-type-definition-v2.factory';
import { RelationConnectInputTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/relation-connect-input-type-definition.factory';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
@ -213,13 +210,6 @@ export class TypeDefinitionsGenerator {
objectMetadataCollection: ObjectMetadataInterface[],
options: WorkspaceBuildSchemaOptions,
) {
const isRelationConnectEnabled = isDefined(options.workspaceId)
? await this.featureFlagService.isFeatureEnabled(
FeatureFlagKey.IS_RELATION_CONNECT_ENABLED,
options.workspaceId,
)
: false;
const inputTypeDefs = objectMetadataCollection
.map((objectMetadata) => {
const optionalExtendedObjectMetadata = {
@ -236,13 +226,11 @@ export class TypeDefinitionsGenerator {
objectMetadata,
kind: InputTypeDefinitionKind.Create,
options,
isRelationConnectEnabled,
}),
// Input type for update
this.inputTypeDefinitionFactory.create({
objectMetadata: optionalExtendedObjectMetadata,
kind: InputTypeDefinitionKind.Update,
isRelationConnectEnabled,
options,
}),
// Filter input type

View File

@ -47,13 +47,11 @@ export const generateFields = <
kind,
options,
typeFactory,
isRelationConnectEnabled = false,
}: {
objectMetadata: ObjectMetadataInterface;
kind: T;
options: WorkspaceBuildSchemaOptions;
typeFactory: TypeFactory<T>;
isRelationConnectEnabled?: boolean;
}): T extends InputTypeDefinitionKind
? GraphQLInputFieldConfigMap
: // eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -80,7 +78,6 @@ export const generateFields = <
kind,
options,
typeFactory,
isRelationConnectEnabled,
});
} else {
generatedField = generateField({
@ -168,7 +165,6 @@ const generateRelationField = <
kind,
options,
typeFactory,
isRelationConnectEnabled,
}: {
fieldMetadata: FieldMetadataInterface<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
@ -176,7 +172,6 @@ const generateRelationField = <
kind: T;
options: WorkspaceBuildSchemaOptions;
typeFactory: TypeFactory<T>;
isRelationConnectEnabled: boolean;
}) => {
const relationField = {};
@ -208,11 +203,10 @@ const generateRelationField = <
};
if (
[InputTypeDefinitionKind.Create, InputTypeDefinitionKind.Update].includes(
[InputTypeDefinitionKind.Create].includes(
kind as InputTypeDefinitionKind,
) &&
isDefined(fieldMetadata.relationTargetObjectMetadataId) &&
isRelationConnectEnabled
isDefined(fieldMetadata.relationTargetObjectMetadataId)
) {
type = typeFactory.create(
formatRelationConnectInputTarget(

View File

@ -81,9 +81,6 @@ export class WorkspaceSchemaFactory {
await this.workspaceGraphQLSchemaFactory.create(
objectMetadataCollection,
workspaceResolverBuilderMethodNames,
{
workspaceId: authContext.workspace.id,
},
);
usedScalarNames =