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:
@ -37,12 +37,22 @@ import {
|
||||
PermissionsExceptionCode,
|
||||
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||
import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
|
||||
import { QueryDeepPartialEntityWithRelationConnect } from 'src/engine/twenty-orm/entity-manager/types/query-deep-partial-entity-with-relation-connect.type';
|
||||
import { RelationConnectQueryConfig } from 'src/engine/twenty-orm/entity-manager/types/relation-connect-query-config.type';
|
||||
import {
|
||||
TwentyORMException,
|
||||
TwentyORMExceptionCode,
|
||||
} from 'src/engine/twenty-orm/exceptions/twenty-orm.exception';
|
||||
import {
|
||||
OperationType,
|
||||
validateOperationIsPermittedOrThrow,
|
||||
} from 'src/engine/twenty-orm/repository/permissions.utils';
|
||||
import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
|
||||
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||
import { computeRelationConnectQueryConfigs } from 'src/engine/twenty-orm/utils/compute-relation-connect-query-configs.util';
|
||||
import { createSqlWhereTupleInClause } from 'src/engine/twenty-orm/utils/create-sql-where-tuple-in-clause.utils';
|
||||
import { getObjectMetadataFromEntityTarget } from 'src/engine/twenty-orm/utils/get-object-metadata-from-entity-target.util';
|
||||
import { getRecordToConnectFields } from 'src/engine/twenty-orm/utils/get-record-to-connect-fields.util';
|
||||
|
||||
type PermissionOptions = {
|
||||
shouldBypassPermissionChecks?: boolean;
|
||||
@ -165,11 +175,21 @@ export class WorkspaceEntityManager extends EntityManager {
|
||||
);
|
||||
}
|
||||
|
||||
override insert<Entity extends ObjectLiteral>(
|
||||
override async insert<Entity extends ObjectLiteral>(
|
||||
target: EntityTarget<Entity>,
|
||||
entity: QueryDeepPartialEntity<Entity> | QueryDeepPartialEntity<Entity>[],
|
||||
entity:
|
||||
| QueryDeepPartialEntityWithRelationConnect<Entity>
|
||||
| QueryDeepPartialEntityWithRelationConnect<Entity>[],
|
||||
permissionOptions?: PermissionOptions,
|
||||
): Promise<InsertResult> {
|
||||
const entityArray = Array.isArray(entity) ? entity : [entity];
|
||||
|
||||
const connectedEntities = await this.processRelationConnect<Entity>(
|
||||
entityArray,
|
||||
target,
|
||||
permissionOptions,
|
||||
);
|
||||
|
||||
return this.createQueryBuilder(
|
||||
undefined,
|
||||
undefined,
|
||||
@ -178,7 +198,7 @@ export class WorkspaceEntityManager extends EntityManager {
|
||||
)
|
||||
.insert()
|
||||
.into(target)
|
||||
.values(entity)
|
||||
.values(connectedEntities)
|
||||
.execute();
|
||||
}
|
||||
|
||||
@ -1321,4 +1341,118 @@ export class WorkspaceEntityManager extends EntityManager {
|
||||
PermissionsExceptionCode.RAW_SQL_NOT_ALLOWED,
|
||||
);
|
||||
}
|
||||
|
||||
private async processRelationConnect<Entity extends ObjectLiteral>(
|
||||
entities: QueryDeepPartialEntityWithRelationConnect<Entity>[],
|
||||
target: EntityTarget<Entity>,
|
||||
permissionOptions?: PermissionOptions,
|
||||
): Promise<QueryDeepPartialEntity<Entity>[]> {
|
||||
const objectMetadata = getObjectMetadataFromEntityTarget(
|
||||
target,
|
||||
this.internalContext,
|
||||
);
|
||||
|
||||
const objectMetadataMap = this.internalContext.objectMetadataMaps;
|
||||
|
||||
const relationConnectQueryConfigs = computeRelationConnectQueryConfigs(
|
||||
entities,
|
||||
objectMetadata,
|
||||
objectMetadataMap,
|
||||
);
|
||||
|
||||
if (!isDefined(relationConnectQueryConfigs)) return entities;
|
||||
|
||||
const recordsToConnectWithConfig = await this.executeConnectQueries(
|
||||
relationConnectQueryConfigs,
|
||||
permissionOptions,
|
||||
);
|
||||
|
||||
const updatedEntities = this.updateEntitiesWithRecordToConnectId<Entity>(
|
||||
entities,
|
||||
recordsToConnectWithConfig,
|
||||
);
|
||||
|
||||
return updatedEntities;
|
||||
}
|
||||
|
||||
private async executeConnectQueries(
|
||||
relationConnectQueryConfigs: Record<string, RelationConnectQueryConfig>,
|
||||
permissionOptions?: PermissionOptions,
|
||||
): Promise<[RelationConnectQueryConfig, Record<string, unknown>[]][]> {
|
||||
const AllRecordsToConnectWithConfig: [
|
||||
RelationConnectQueryConfig,
|
||||
Record<string, unknown>[],
|
||||
][] = [];
|
||||
|
||||
for (const connectQueryConfig of Object.values(
|
||||
relationConnectQueryConfigs,
|
||||
)) {
|
||||
const { clause, parameters } = createSqlWhereTupleInClause(
|
||||
connectQueryConfig.recordToConnectConditions,
|
||||
connectQueryConfig.targetObjectName,
|
||||
);
|
||||
|
||||
const recordsToConnect = await this.createQueryBuilder(
|
||||
connectQueryConfig.targetObjectName,
|
||||
connectQueryConfig.targetObjectName,
|
||||
undefined,
|
||||
permissionOptions,
|
||||
)
|
||||
.select(getRecordToConnectFields(connectQueryConfig))
|
||||
.where(clause, parameters)
|
||||
.getRawMany();
|
||||
|
||||
AllRecordsToConnectWithConfig.push([
|
||||
connectQueryConfig,
|
||||
recordsToConnect,
|
||||
]);
|
||||
}
|
||||
|
||||
return AllRecordsToConnectWithConfig;
|
||||
}
|
||||
|
||||
private updateEntitiesWithRecordToConnectId<Entity extends ObjectLiteral>(
|
||||
entities: QueryDeepPartialEntityWithRelationConnect<Entity>[],
|
||||
recordsToConnectWithConfig: [
|
||||
RelationConnectQueryConfig,
|
||||
Record<string, unknown>[],
|
||||
][],
|
||||
): QueryDeepPartialEntity<Entity>[] {
|
||||
return entities.map((entity, index) => {
|
||||
for (const [
|
||||
connectQueryConfig,
|
||||
recordsToConnect,
|
||||
] of recordsToConnectWithConfig) {
|
||||
if (
|
||||
isDefined(
|
||||
connectQueryConfig.recordToConnectConditionByEntityIndex[index],
|
||||
)
|
||||
) {
|
||||
const recordToConnect = recordsToConnect.filter((record) =>
|
||||
connectQueryConfig.recordToConnectConditionByEntityIndex[
|
||||
index
|
||||
].every(([field, value]) => record[field] === value),
|
||||
);
|
||||
|
||||
if (recordToConnect.length !== 1) {
|
||||
const recordToConnectTotal = recordToConnect.length;
|
||||
const connectFieldName = connectQueryConfig.connectFieldName;
|
||||
|
||||
throw new TwentyORMException(
|
||||
`Expected 1 record to connect to ${connectFieldName}, but found ${recordToConnectTotal}.`,
|
||||
TwentyORMExceptionCode.CONNECT_RECORD_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
entity = {
|
||||
...entity,
|
||||
[connectQueryConfig.relationFieldName]: recordToConnect[0]['id'],
|
||||
[connectQueryConfig.connectFieldName]: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return entity;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user