Allow custom fields to be editable for system objects and prevent the error for timeline activity on system object pages. (#16268)
Solves #16015 - Added a check in `useTimelineActivities.ts` to see if the system object page we're viewing has timelineActivity being tracked before querying to get the activity history. - Removed @WorkspaceIsObjectUIReadOnly decorator from system objects and added @WorkspaceIsFieldUIReadOnly to standard and system fields to allow custom field edits as requested in the issue. - Did not add calendarEvents or other system objects to timelineActivity just yet since keeping track of timeline activity for every one of them felt counter-intuitive and bloated. In order to determine which objects need timelineActivity, I think we need to fix the broken views of system objects first, such as the one in the attached screenshots - a good number of them are broken. The check I added to `useTimelineActivities.ts` hook displays timeline activity as empty for the time - error would not be shown as suggested by Thomas. <img width="1062" height="858" alt="image" src="https://github.com/user-attachments/assets/e877e0fe-b665-46e3-b785-e84f2af7f833" /> <br /> <img width="1061" height="858" alt="image" src="https://github.com/user-attachments/assets/0eba8c1c-444a-4b13-beda-64b95cf39077" /> - Editing custom fields on calendar events (and other objects without position fields) crashed with TypeError: Cannot convert undefined or null to object in sortCachedObjectEdges. This happened because some cached queries had empty orderBy arrays ([]), and the optimistic effect tried to sort with them. Objects without position fields returned empty orderBy arrays when there were no sorts, while objects with position fields automatically got [{ position: 'AscNullsFirst' }]. The backend always adds { id: 'AscNullsFirst' } as a fallback, but the frontend didn't match this. So, I added { id: 'AscNullsFirst' } to the Frontend as default orderBy when there are no sorts and no position field. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Apply per-field UI read-only to system/standard fields and skip timeline activity fetching when the target object isn’t related; refine record-table cell open/navigation behavior. > > - Server: Replace object-level UI read-only with per-field `isUIReadOnly` across many standard objects (e.g., `calendarEvent`, `workspaceMember`, messaging, calendar, favorites, attachments, workflows, etc.), and mirror this via `@WorkspaceIsFieldUIReadOnly` on workspace entities. > - Frontend: In `useTimelineActivities.ts`, check object metadata for a relation to `timelineActivity` and `skip` the query when absent, preventing errors on system object pages. > - Frontend: Simplify/adjust record-table cell logic—remove unused args, allow navigation from first column when non-empty, block editing for read-only records (while still allowing navigation), and update button handlers/hover styles accordingly. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit dac1262d70d97ce84b39730454dd3579c494994a. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
+4
@@ -0,0 +1,4 @@
|
||||
export const SYSTEM_OBJECTS_WITH_TIMELINE_ACTIVITIES = [
|
||||
'noteTarget',
|
||||
'taskTarget',
|
||||
];
|
||||
+4
-2
@@ -8,6 +8,7 @@ import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queu
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import { WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event-batch.type';
|
||||
import { SYSTEM_OBJECTS_WITH_TIMELINE_ACTIVITIES } from 'src/modules/timeline/constants/system-objects-with-timeline-activities.constant';
|
||||
import { TimelineActivityService } from 'src/modules/timeline/services/timeline-activity.service';
|
||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
|
||||
@@ -28,8 +29,9 @@ export class UpsertTimelineActivityFromInternalEvent {
|
||||
|
||||
if (
|
||||
workspaceEventBatch.objectMetadata.isSystem &&
|
||||
workspaceEventBatch.objectMetadata.nameSingular !== 'noteTarget' &&
|
||||
workspaceEventBatch.objectMetadata.nameSingular !== 'taskTarget'
|
||||
!SYSTEM_OBJECTS_WITH_TIMELINE_ACTIVITIES.includes(
|
||||
workspaceEventBatch.objectMetadata.nameSingular,
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
+17
@@ -11,6 +11,7 @@ import { CustomWorkspaceEntity } from 'src/engine/twenty-orm/custom.workspace-en
|
||||
import { WorkspaceDynamicRelation } from 'src/engine/twenty-orm/decorators/workspace-dynamic-relation.decorator';
|
||||
import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator';
|
||||
import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator';
|
||||
import { WorkspaceIsFieldUIReadOnly } from 'src/engine/twenty-orm/decorators/workspace-is-field-ui-readonly.decorator';
|
||||
import { WorkspaceIsNotAuditLogged } from 'src/engine/twenty-orm/decorators/workspace-is-not-audit-logged.decorator';
|
||||
import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator';
|
||||
import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator';
|
||||
@@ -50,6 +51,7 @@ export class TimelineActivityWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
icon: 'IconCalendar',
|
||||
defaultValue: 'now',
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
happensAt: Date;
|
||||
|
||||
@WorkspaceField({
|
||||
@@ -59,6 +61,7 @@ export class TimelineActivityWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`Event name`,
|
||||
icon: 'IconAbc',
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsNullable()
|
||||
name: string | null;
|
||||
|
||||
@@ -69,6 +72,7 @@ export class TimelineActivityWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`Json value for event details`,
|
||||
icon: 'IconListDetails',
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsNullable()
|
||||
properties: JSON | null;
|
||||
|
||||
@@ -80,6 +84,7 @@ export class TimelineActivityWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`Cached record name`,
|
||||
icon: 'IconAbc',
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsNullable()
|
||||
linkedRecordCachedName: string | null;
|
||||
|
||||
@@ -90,6 +95,7 @@ export class TimelineActivityWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`Linked Record id`,
|
||||
icon: 'IconAbc',
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsNullable()
|
||||
linkedRecordId: string | null;
|
||||
|
||||
@@ -100,6 +106,7 @@ export class TimelineActivityWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`Linked Object Metadata Id`,
|
||||
icon: 'IconAbc',
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsNullable()
|
||||
linkedObjectMetadataId: string | null;
|
||||
|
||||
@@ -114,6 +121,7 @@ export class TimelineActivityWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
inverseSideFieldKey: 'timelineActivities',
|
||||
onDelete: RelationOnDeleteAction.CASCADE,
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsNullable()
|
||||
workspaceMember: Relation<WorkspaceMemberWorkspaceEntity> | null;
|
||||
|
||||
@@ -132,6 +140,7 @@ export class TimelineActivityWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
isMorphRelation: true,
|
||||
morphId: TIMELINE_ACTIVITY_STANDARD_FIELD_IDS.targetMorphId,
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsNullable()
|
||||
targetPerson: Relation<PersonWorkspaceEntity> | null;
|
||||
|
||||
@@ -150,6 +159,7 @@ export class TimelineActivityWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
isMorphRelation: true,
|
||||
morphId: TIMELINE_ACTIVITY_STANDARD_FIELD_IDS.targetMorphId,
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsNullable()
|
||||
targetCompany: Relation<CompanyWorkspaceEntity> | null;
|
||||
|
||||
@@ -168,6 +178,7 @@ export class TimelineActivityWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
isMorphRelation: true,
|
||||
morphId: TIMELINE_ACTIVITY_STANDARD_FIELD_IDS.targetMorphId,
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsNullable()
|
||||
targetOpportunity: Relation<OpportunityWorkspaceEntity> | null;
|
||||
|
||||
@@ -186,6 +197,7 @@ export class TimelineActivityWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
isMorphRelation: true,
|
||||
morphId: TIMELINE_ACTIVITY_STANDARD_FIELD_IDS.targetMorphId,
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsNullable()
|
||||
targetNote: Relation<NoteWorkspaceEntity> | null;
|
||||
|
||||
@@ -204,6 +216,7 @@ export class TimelineActivityWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
isMorphRelation: true,
|
||||
morphId: TIMELINE_ACTIVITY_STANDARD_FIELD_IDS.targetMorphId,
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsNullable()
|
||||
targetTask: Relation<TaskWorkspaceEntity> | null;
|
||||
|
||||
@@ -222,6 +235,7 @@ export class TimelineActivityWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
isMorphRelation: true,
|
||||
morphId: TIMELINE_ACTIVITY_STANDARD_FIELD_IDS.targetMorphId,
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsNullable()
|
||||
targetWorkflow: Relation<WorkflowWorkspaceEntity> | null;
|
||||
|
||||
@@ -240,6 +254,7 @@ export class TimelineActivityWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
isMorphRelation: true,
|
||||
morphId: TIMELINE_ACTIVITY_STANDARD_FIELD_IDS.targetMorphId,
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsNullable()
|
||||
targetWorkflowVersion: Relation<WorkflowVersionWorkspaceEntity> | null;
|
||||
|
||||
@@ -258,6 +273,7 @@ export class TimelineActivityWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
isMorphRelation: true,
|
||||
morphId: TIMELINE_ACTIVITY_STANDARD_FIELD_IDS.targetMorphId,
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsNullable()
|
||||
targetWorkflowRun: Relation<WorkflowRunWorkspaceEntity> | null;
|
||||
|
||||
@@ -276,6 +292,7 @@ export class TimelineActivityWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
isMorphRelation: true,
|
||||
morphId: TIMELINE_ACTIVITY_STANDARD_FIELD_IDS.targetMorphId,
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsNullable()
|
||||
targetDashboard: Relation<DashboardWorkspaceEntity> | null;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user