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:
+23
@@ -19,6 +19,7 @@ import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity
|
||||
import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator';
|
||||
import { WorkspaceFieldIndex } from 'src/engine/twenty-orm/decorators/workspace-field-index.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 { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator';
|
||||
import { WorkspaceIsSearchable } from 'src/engine/twenty-orm/decorators/workspace-is-searchable.decorator';
|
||||
import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator';
|
||||
@@ -107,6 +108,7 @@ export class WorkspaceMemberWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
icon: 'IconHierarchy2',
|
||||
defaultValue: 0,
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsSystem()
|
||||
position: number;
|
||||
|
||||
@@ -117,6 +119,7 @@ export class WorkspaceMemberWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`Workspace member name`,
|
||||
icon: 'IconCircleUser',
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
name: FullNameMetadata;
|
||||
|
||||
@WorkspaceField({
|
||||
@@ -127,6 +130,7 @@ export class WorkspaceMemberWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
icon: 'IconColorSwatch',
|
||||
defaultValue: "'System'",
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsSystem()
|
||||
colorScheme: string;
|
||||
|
||||
@@ -138,6 +142,7 @@ export class WorkspaceMemberWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
icon: 'IconLanguage',
|
||||
defaultValue: `'${SOURCE_LOCALE}'`,
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsSystem()
|
||||
locale: keyof typeof APP_LOCALES;
|
||||
|
||||
@@ -148,6 +153,7 @@ export class WorkspaceMemberWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`Workspace member avatar`,
|
||||
icon: 'IconFileUpload',
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsNullable()
|
||||
@WorkspaceIsSystem()
|
||||
avatarUrl: string | null;
|
||||
@@ -160,6 +166,7 @@ export class WorkspaceMemberWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`Related user email address`,
|
||||
icon: 'IconMail',
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsNullable()
|
||||
@WorkspaceIsSystem()
|
||||
userEmail: string | null;
|
||||
@@ -174,6 +181,7 @@ export class WorkspaceMemberWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
dataType: NumberDataType.INT,
|
||||
},
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsSystem()
|
||||
calendarStartDay: number;
|
||||
|
||||
@@ -184,6 +192,7 @@ export class WorkspaceMemberWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`Associated User Id`,
|
||||
icon: 'IconCircleUsers',
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsSystem()
|
||||
userId: string;
|
||||
|
||||
@@ -195,6 +204,7 @@ export class WorkspaceMemberWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`User time zone`,
|
||||
icon: 'IconTimezone',
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsSystem()
|
||||
timeZone: string;
|
||||
|
||||
@@ -232,6 +242,7 @@ export class WorkspaceMemberWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
],
|
||||
defaultValue: `'${WorkspaceMemberDateFormatEnum.SYSTEM}'`,
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsSystem()
|
||||
dateFormat: string;
|
||||
|
||||
@@ -263,6 +274,7 @@ export class WorkspaceMemberWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
],
|
||||
defaultValue: `'${WorkspaceMemberTimeFormatEnum.SYSTEM}'`,
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsSystem()
|
||||
timeFormat: string;
|
||||
|
||||
@@ -277,6 +289,7 @@ export class WorkspaceMemberWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
inverseSideFieldKey: 'assignee',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
assignedTasks: Relation<TaskWorkspaceEntity[]>;
|
||||
|
||||
@WorkspaceRelation({
|
||||
@@ -289,6 +302,7 @@ export class WorkspaceMemberWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
inverseSideFieldKey: 'forWorkspaceMember',
|
||||
onDelete: RelationOnDeleteAction.CASCADE,
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
favorites: Relation<FavoriteWorkspaceEntity[]>;
|
||||
|
||||
@WorkspaceRelation({
|
||||
@@ -301,6 +315,7 @@ export class WorkspaceMemberWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
inverseSideFieldKey: 'accountOwner',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
accountOwnerForCompanies: Relation<CompanyWorkspaceEntity[]>;
|
||||
|
||||
@WorkspaceRelation({
|
||||
@@ -313,6 +328,7 @@ export class WorkspaceMemberWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
inverseSideFieldKey: 'author',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsSystem()
|
||||
authoredAttachments: Relation<AttachmentWorkspaceEntity[]>;
|
||||
|
||||
@@ -326,6 +342,7 @@ export class WorkspaceMemberWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
inverseSideFieldKey: 'accountOwner',
|
||||
onDelete: RelationOnDeleteAction.CASCADE,
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
connectedAccounts: Relation<ConnectedAccountWorkspaceEntity[]>;
|
||||
|
||||
@WorkspaceRelation({
|
||||
@@ -338,6 +355,7 @@ export class WorkspaceMemberWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
inverseSideFieldKey: 'workspaceMember',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
messageParticipants: Relation<MessageParticipantWorkspaceEntity[]>;
|
||||
|
||||
@WorkspaceRelation({
|
||||
@@ -350,6 +368,7 @@ export class WorkspaceMemberWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
inverseSideFieldKey: 'workspaceMember',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
blocklist: Relation<BlocklistWorkspaceEntity[]>;
|
||||
|
||||
@WorkspaceRelation({
|
||||
@@ -362,6 +381,7 @@ export class WorkspaceMemberWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
inverseSideFieldKey: 'workspaceMember',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
calendarEventParticipants: Relation<
|
||||
CalendarEventParticipantWorkspaceEntity[]
|
||||
>;
|
||||
@@ -375,6 +395,7 @@ export class WorkspaceMemberWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
inverseSideTarget: () => TimelineActivityWorkspaceEntity,
|
||||
onDelete: RelationOnDeleteAction.CASCADE,
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsNullable()
|
||||
@WorkspaceIsSystem()
|
||||
timelineActivities: Relation<TimelineActivityWorkspaceEntity[]>;
|
||||
@@ -390,6 +411,7 @@ export class WorkspaceMemberWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
SEARCH_FIELDS_FOR_WORKSPACE_MEMBER,
|
||||
),
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsNullable()
|
||||
@WorkspaceIsSystem()
|
||||
@WorkspaceFieldIndex({ indexType: IndexType.GIN })
|
||||
@@ -435,6 +457,7 @@ export class WorkspaceMemberWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
],
|
||||
defaultValue: `'${WorkspaceMemberNumberFormatEnum.SYSTEM}'`,
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsSystem()
|
||||
numberFormat: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user