Compare commits

...
Author SHA1 Message Date
sonarly-bot 138b1eae67 fix(activities): resolve target filter field from metadata
https://sonarly.com/issue/42158?type=bug

Adding notes/tasks fails on some custom-object record pages because the frontend builds a filter key that does not exist on `noteTarget`/`taskTarget` anymore. The backend correctly rejects the invalid filter with `INVALID_ARGS_FILTER`.

Fix: I first checked recent history for an already-merged fix in the affected files (`git log --all --oneline --since='30 days ago' -- ...`) and found no candidate in the last 30 days.

I then implemented the fix in the frontend activity-target filter construction layer (the actual failing layer):

1) Restored metadata-based join-column lookup in `getActivityTargetsFilter`
- File: `packages/twenty-front/src/modules/activities/utils/getActivityTargetsFilter.ts`
- Instead of always computing `target${CurrentObjectName}Id`, the filter now:
  - resolves whether we are querying `noteTarget` or `taskTarget`,
  - finds the corresponding activity-target object metadata,
  - finds the relation field targeting the current target object metadata id,
  - uses that field’s persisted `settings.joinColumnName` as the filter key.
- This handles renamed custom objects where persisted join column names no longer match current object names.

2) Updated the only caller to pass required metadata context
- File: `packages/twenty-front/src/modules/activities/hooks/useActivityTargetsForTargetableObjects.ts`
- `getActivityTargetsFilter` now receives:
  - `activityObjectNameSingular`
  - `objectMetadataItems`
  so it can resolve the correct metadata join column.

3) Added regression unit tests
- File: `packages/twenty-front/src/modules/activities/utils/__tests__/getActivityTargetsFilter.test.ts`
- Added tests asserting that filter keys come from metadata join-column names (not recomputed names), for both Note and Task target flows.

Validation attempts:
- `npx jest ...getActivityTargetsFilter.test.ts --config=packages/twenty-front/jest.config.mjs` failed due to missing `ts-jest` in this environment.
- `npx nx lint:diff-with-main twenty-front` failed because Nx modules are not installed in this environment.

Authored by Sonarly by autonomous analysis (run 48109).
2026-06-02 19:48:46 +00:00
3 changed files with 124 additions and 4 deletions
@@ -36,6 +36,8 @@ export const useActivityTargetsForTargetableObjects = ({
);
const activityTargetsFilter = getActivityTargetsFilter({
targetableObjects,
activityObjectNameSingular: objectNameSingular,
objectMetadataItems,
});
const FIND_ACTIVITY_TARGETS_OPERATION_SIGNATURE =
@@ -0,0 +1,86 @@
import { getActivityTargetsFilter } from '@/activities/utils/getActivityTargetsFilter';
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
import { CoreObjectNameSingular } from 'twenty-shared/types';
const objectMetadataItems = [
{
id: 'note-target-id',
nameSingular: CoreObjectNameSingular.NoteTarget,
fields: [
{
relation: {
targetObjectMetadata: {
id: 'custom-object-id',
},
},
settings: {
joinColumnName: 'targetLegacyCustomObjectId',
},
},
],
},
{
id: 'task-target-id',
nameSingular: CoreObjectNameSingular.TaskTarget,
fields: [
{
relation: {
targetObjectMetadata: {
id: 'custom-object-id',
},
},
settings: {
joinColumnName: 'targetTaskLegacyCustomObjectId',
},
},
],
},
{
id: 'custom-object-id',
nameSingular: 'customObjectRenamed',
fields: [],
},
] as unknown as Pick<
EnrichedObjectMetadataItem,
'nameSingular' | 'fields' | 'id'
>[];
describe('getActivityTargetsFilter', () => {
it('should use joinColumnName from note target metadata when object was renamed', () => {
const filter = getActivityTargetsFilter({
targetableObjects: [
{
id: 'record-id',
targetObjectNameSingular: 'customObjectRenamed',
},
],
activityObjectNameSingular: CoreObjectNameSingular.Note,
objectMetadataItems,
});
expect(filter).toEqual({
targetLegacyCustomObjectId: {
eq: 'record-id',
},
});
});
it('should use task target metadata when filtering task targets', () => {
const filter = getActivityTargetsFilter({
targetableObjects: [
{
id: 'record-id',
targetObjectNameSingular: 'customObjectRenamed',
},
],
activityObjectNameSingular: CoreObjectNameSingular.Task,
objectMetadataItems,
});
expect(filter).toEqual({
targetTaskLegacyCustomObjectId: {
eq: 'record-id',
},
});
});
});
@@ -1,21 +1,53 @@
import { type ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import { getActivityTargetObjectFieldIdName } from '@/activities/utils/getActivityTargetObjectFieldIdName';
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
import { CoreObjectNameSingular } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
export const getActivityTargetsFilter = ({
targetableObjects,
activityObjectNameSingular,
objectMetadataItems,
}: {
targetableObjects: Pick<
ActivityTargetableObject,
'id' | 'targetObjectNameSingular'
>[];
activityObjectNameSingular:
| CoreObjectNameSingular.Note
| CoreObjectNameSingular.Task;
objectMetadataItems: Pick<EnrichedObjectMetadataItem, 'nameSingular' | 'fields' | 'id'>[];
}) => {
const activityTargetObjectNameSingular =
activityObjectNameSingular === CoreObjectNameSingular.Task
? CoreObjectNameSingular.TaskTarget
: CoreObjectNameSingular.NoteTarget;
const activityTargetObjectMetadata = objectMetadataItems.find(
(objectMetadataItem) =>
objectMetadataItem.nameSingular === activityTargetObjectNameSingular,
);
const findManyActivityTargetsQueryFilter = Object.fromEntries(
targetableObjects
.map((targetableObject) => {
const joinColumnName = getActivityTargetObjectFieldIdName({
nameSingular: targetableObject.targetObjectNameSingular,
});
const targetObjectMetadataItem = objectMetadataItems.find(
(item) =>
item.nameSingular === targetableObject.targetObjectNameSingular,
);
if (!isDefined(targetObjectMetadataItem)) {
return undefined;
}
const joinColumnName = activityTargetObjectMetadata?.fields.find(
(field) =>
field.relation?.targetObjectMetadata.id ===
targetObjectMetadataItem.id,
)?.settings?.joinColumnName;
if (!isDefined(joinColumnName)) {
return undefined;
}
return [
joinColumnName,