Set formatted before instead of after (#15401)

Soft deletion events use before in event properties. But we set the
after instead, that only contains a few data.

Fixes https://github.com/twentyhq/twenty/issues/15120

Debugging raised an additional issue. UpdateMany, RestoreMany and
SoftDeleteMany were broken for custom objects. The `where` clause was
using "_objectName.id" instead of "objectName.id" during select. That's
why we need to use different where clause between the select of the
existing value and the actual mutation.

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Thomas Trompette
2025-10-28 17:13:39 +01:00
committed by GitHub
parent 0d896fdca8
commit 1590751131
5 changed files with 55 additions and 23 deletions
@@ -14,7 +14,6 @@ import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/g
import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return';
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
@Injectable()
export class GraphqlQueryDeleteManyResolverService extends GraphqlQueryBaseResolverService<
@@ -31,14 +30,9 @@ export class GraphqlQueryDeleteManyResolverService extends GraphqlQueryBaseResol
objectMetadataItemWithFieldMaps.nameSingular,
);
const tableName = computeTableName(
objectMetadataItemWithFieldMaps.nameSingular,
objectMetadataItemWithFieldMaps.isCustom,
);
executionArgs.graphqlQueryParser.applyFilterToBuilder(
queryBuilder,
tableName,
objectMetadataItemWithFieldMaps.nameSingular,
executionArgs.args.filter,
);
@@ -14,7 +14,6 @@ import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/g
import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return';
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
@Injectable()
export class GraphqlQueryRestoreManyResolverService extends GraphqlQueryBaseResolverService<
@@ -31,14 +30,9 @@ export class GraphqlQueryRestoreManyResolverService extends GraphqlQueryBaseReso
objectMetadataItemWithFieldMaps.nameSingular,
);
const tableName = computeTableName(
objectMetadataItemWithFieldMaps.nameSingular,
objectMetadataItemWithFieldMaps.isCustom,
);
executionArgs.graphqlQueryParser.applyFilterToBuilder(
queryBuilder,
tableName,
objectMetadataItemWithFieldMaps.nameSingular,
executionArgs.args.filter,
);
@@ -14,7 +14,6 @@ import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/g
import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return';
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
@Injectable()
export class GraphqlQueryUpdateManyResolverService extends GraphqlQueryBaseResolverService<
@@ -31,14 +30,9 @@ export class GraphqlQueryUpdateManyResolverService extends GraphqlQueryBaseResol
objectMetadataItemWithFieldMaps.nameSingular,
);
const tableName = computeTableName(
objectMetadataItemWithFieldMaps.nameSingular,
objectMetadataItemWithFieldMaps.isCustom,
);
executionArgs.graphqlQueryParser.applyFilterToBuilder(
queryBuilder,
tableName,
objectMetadataItemWithFieldMaps.nameSingular,
executionArgs.args.filter,
);
@@ -6,6 +6,7 @@ import {
type UpdateResult,
} from 'typeorm';
import { SoftDeleteQueryBuilder } from 'typeorm/query-builder/SoftDeleteQueryBuilder';
import { type WhereClause } from 'typeorm/query-builder/WhereClause';
import { type FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
import { type WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
@@ -19,11 +20,13 @@ import {
} from 'src/engine/twenty-orm/exceptions/twenty-orm.exception';
import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.utils';
import { type WorkspaceDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-delete-query-builder';
import { type WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
import { type WorkspaceUpdateQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-update-query-builder';
import { applyTableAliasOnWhereCondition } from 'src/engine/twenty-orm/utils/apply-table-alias-on-where-condition';
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
import { formatTwentyOrmEventToDatabaseBatchEvent } from 'src/engine/twenty-orm/utils/format-twenty-orm-event-to-database-batch-event.util';
import { getObjectMetadataFromEntityTarget } from 'src/engine/twenty-orm/utils/get-object-metadata-from-entity-target.util';
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
export class WorkspaceSoftDeleteQueryBuilder<
T extends ObjectLiteral,
@@ -78,6 +81,33 @@ export class WorkspaceSoftDeleteQueryBuilder<
this.internalContext,
);
const eventSelectQueryBuilder = new WorkspaceSelectQueryBuilder(
this as unknown as WorkspaceSelectQueryBuilder<T>,
this.objectRecordsPermissions,
this.internalContext,
true,
this.authContext,
this.featureFlagMap,
);
const tableName = computeTableName(
objectMetadata.nameSingular,
objectMetadata.isCustom,
);
eventSelectQueryBuilder.expressionMap.wheres = this.expressionMap.wheres;
eventSelectQueryBuilder.expressionMap.aliases =
this.expressionMap.aliases;
eventSelectQueryBuilder.setParameters(this.getParameters());
const before = await eventSelectQueryBuilder.getMany();
this.expressionMap.wheres = applyTableAliasOnWhereCondition({
condition: this.expressionMap.wheres,
tableName,
aliasName: objectMetadata.nameSingular,
}) as WhereClause[];
const after = await super.execute();
const formattedAfter = formatResult<T[]>(
@@ -86,12 +116,18 @@ export class WorkspaceSoftDeleteQueryBuilder<
this.internalContext.objectMetadataMaps,
);
const formattedBefore = formatResult<T[]>(
before,
objectMetadata,
this.internalContext.objectMetadataMaps,
);
this.internalContext.eventEmitterService.emitDatabaseBatchEvent(
formatTwentyOrmEventToDatabaseBatchEvent({
action: DatabaseEventAction.DELETED,
objectMetadataItem: objectMetadata,
workspaceId: this.internalContext.workspaceId,
entities: formattedAfter,
entities: formattedBefore,
authContext: this.authContext,
}),
);
@@ -7,6 +7,7 @@ import {
type UpdateResult,
} from 'typeorm';
import { type QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
import { type WhereClause } from 'typeorm/query-builder/WhereClause';
import { type FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
import { type WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
@@ -26,10 +27,12 @@ import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repositor
import { type WorkspaceDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-delete-query-builder';
import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
import { type WorkspaceSoftDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-soft-delete-query-builder';
import { applyTableAliasOnWhereCondition } from 'src/engine/twenty-orm/utils/apply-table-alias-on-where-condition';
import { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
import { formatTwentyOrmEventToDatabaseBatchEvent } from 'src/engine/twenty-orm/utils/format-twenty-orm-event-to-database-batch-event.util';
import { getObjectMetadataFromEntityTarget } from 'src/engine/twenty-orm/utils/get-object-metadata-from-entity-target.util';
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
export class WorkspaceUpdateQueryBuilder<
T extends ObjectLiteral,
@@ -109,6 +112,11 @@ export class WorkspaceUpdateQueryBuilder<
this.featureFlagMap,
);
const tableName = computeTableName(
objectMetadata.nameSingular,
objectMetadata.isCustom,
);
eventSelectQueryBuilder.expressionMap.wheres = this.expressionMap.wheres;
eventSelectQueryBuilder.expressionMap.aliases =
this.expressionMap.aliases;
@@ -116,6 +124,12 @@ export class WorkspaceUpdateQueryBuilder<
const before = await eventSelectQueryBuilder.getMany();
this.expressionMap.wheres = applyTableAliasOnWhereCondition({
condition: this.expressionMap.wheres,
tableName,
aliasName: objectMetadata.nameSingular,
}) as WhereClause[];
const nestedRelationQueryBuilder = new WorkspaceSelectQueryBuilder(
this as unknown as WorkspaceSelectQueryBuilder<T>,
this.objectRecordsPermissions,