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
dac1262d70. 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:
co-authored by
Félix Malfait
parent
5003fc4196
commit
c6ea7ae288
+4
@@ -8,6 +8,7 @@ import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfa
|
||||
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
|
||||
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 { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator';
|
||||
import { WorkspaceJoinColumn } from 'src/engine/twenty-orm/decorators/workspace-join-column.decorator';
|
||||
import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator';
|
||||
@@ -53,6 +54,7 @@ export class WorkflowAutomatedTriggerWorkspaceEntity extends BaseWorkspaceEntity
|
||||
},
|
||||
],
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
type: AutomatedTriggerType;
|
||||
|
||||
@WorkspaceField({
|
||||
@@ -61,6 +63,7 @@ export class WorkflowAutomatedTriggerWorkspaceEntity extends BaseWorkspaceEntity
|
||||
label: msg`Settings`,
|
||||
description: msg`The workflow automated trigger settings`,
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
settings: AutomatedTriggerSettings;
|
||||
|
||||
@WorkspaceRelation({
|
||||
@@ -73,6 +76,7 @@ export class WorkflowAutomatedTriggerWorkspaceEntity extends BaseWorkspaceEntity
|
||||
inverseSideFieldKey: 'automatedTriggers',
|
||||
onDelete: RelationOnDeleteAction.CASCADE,
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
workflow: Relation<WorkflowWorkspaceEntity>;
|
||||
|
||||
@WorkspaceJoinColumn('workflow')
|
||||
|
||||
+14
-2
@@ -18,9 +18,9 @@ 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 { 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 { WorkspaceIsObjectUIReadOnly } from 'src/engine/twenty-orm/decorators/workspace-is-object-ui-readonly.decorator';
|
||||
import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator';
|
||||
import { WorkspaceJoinColumn } from 'src/engine/twenty-orm/decorators/workspace-join-column.decorator';
|
||||
import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator';
|
||||
@@ -94,7 +94,6 @@ export const SEARCH_FIELDS_FOR_WORKFLOW_RUNS: FieldTypeAndNameMetadata[] = [
|
||||
})
|
||||
@WorkspaceIsSystem()
|
||||
@WorkspaceIsNotAuditLogged()
|
||||
@WorkspaceIsObjectUIReadOnly()
|
||||
export class WorkflowRunWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
@WorkspaceField({
|
||||
standardId: WORKFLOW_RUN_STANDARD_FIELD_IDS.name,
|
||||
@@ -103,6 +102,7 @@ export class WorkflowRunWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`Name of the workflow run`,
|
||||
icon: 'IconSettingsAutomation',
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsNullable()
|
||||
name: string | null;
|
||||
|
||||
@@ -113,6 +113,7 @@ export class WorkflowRunWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`Workflow run enqueued at`,
|
||||
icon: 'IconHistory',
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsNullable()
|
||||
enqueuedAt: Date | null;
|
||||
|
||||
@@ -123,6 +124,7 @@ export class WorkflowRunWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`Workflow run started at`,
|
||||
icon: 'IconHistory',
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsNullable()
|
||||
startedAt: string | null;
|
||||
|
||||
@@ -133,6 +135,7 @@ export class WorkflowRunWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`Workflow run ended at`,
|
||||
icon: 'IconHistory',
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsNullable()
|
||||
endedAt: string | null;
|
||||
|
||||
@@ -188,6 +191,7 @@ export class WorkflowRunWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
],
|
||||
defaultValue: "'NOT_STARTED'",
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
status: WorkflowRunStatus;
|
||||
|
||||
@WorkspaceField({
|
||||
@@ -197,6 +201,7 @@ export class WorkflowRunWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
icon: 'IconCreativeCommonsSa',
|
||||
description: msg`The executor of the workflow`,
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
createdBy: ActorMetadata;
|
||||
|
||||
@WorkspaceField({
|
||||
@@ -206,6 +211,7 @@ export class WorkflowRunWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`State of the workflow run`,
|
||||
icon: 'IconHierarchy2',
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
state: WorkflowRunState;
|
||||
|
||||
@WorkspaceField({
|
||||
@@ -216,6 +222,7 @@ export class WorkflowRunWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
icon: 'IconHierarchy2',
|
||||
defaultValue: 0,
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsSystem()
|
||||
position: number;
|
||||
|
||||
@@ -230,6 +237,7 @@ export class WorkflowRunWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
SEARCH_FIELDS_FOR_WORKFLOW_RUNS,
|
||||
),
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsNullable()
|
||||
@WorkspaceIsSystem()
|
||||
@WorkspaceFieldIndex({ indexType: IndexType.GIN })
|
||||
@@ -246,6 +254,7 @@ export class WorkflowRunWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
inverseSideFieldKey: 'runs',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
workflowVersion: Relation<WorkflowVersionWorkspaceEntity>;
|
||||
|
||||
@WorkspaceJoinColumn('workflowVersion')
|
||||
@@ -261,6 +270,7 @@ export class WorkflowRunWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
inverseSideFieldKey: 'runs',
|
||||
onDelete: RelationOnDeleteAction.CASCADE,
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
workflow: Relation<WorkflowWorkspaceEntity>;
|
||||
|
||||
@WorkspaceJoinColumn('workflow')
|
||||
@@ -275,6 +285,7 @@ export class WorkflowRunWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
inverseSideTarget: () => FavoriteWorkspaceEntity,
|
||||
onDelete: RelationOnDeleteAction.CASCADE,
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsSystem()
|
||||
favorites: Relation<FavoriteWorkspaceEntity[]>;
|
||||
|
||||
@@ -287,6 +298,7 @@ export class WorkflowRunWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
inverseSideFieldKey: 'targetWorkflowRun',
|
||||
onDelete: RelationOnDeleteAction.CASCADE,
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsSystem()
|
||||
timelineActivities: Relation<TimelineActivityWorkspaceEntity[]>;
|
||||
}
|
||||
|
||||
+11
-2
@@ -12,8 +12,8 @@ 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 { WorkspaceIsObjectUIReadOnly } from 'src/engine/twenty-orm/decorators/workspace-is-object-ui-readonly.decorator';
|
||||
import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator';
|
||||
import { WorkspaceJoinColumn } from 'src/engine/twenty-orm/decorators/workspace-join-column.decorator';
|
||||
import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator';
|
||||
@@ -81,7 +81,6 @@ export const SEARCH_FIELDS_FOR_WORKFLOW_VERSIONS: FieldTypeAndNameMetadata[] = [
|
||||
labelIdentifierStandardId: WORKFLOW_VERSION_STANDARD_FIELD_IDS.name,
|
||||
})
|
||||
@WorkspaceIsSystem()
|
||||
@WorkspaceIsObjectUIReadOnly()
|
||||
export class WorkflowVersionWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
@WorkspaceField({
|
||||
standardId: WORKFLOW_VERSION_STANDARD_FIELD_IDS.name,
|
||||
@@ -90,6 +89,7 @@ export class WorkflowVersionWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`The workflow version name`,
|
||||
icon: 'IconSettingsAutomation',
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsNullable()
|
||||
name: string | null;
|
||||
|
||||
@@ -100,6 +100,7 @@ export class WorkflowVersionWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`Json object to provide trigger`,
|
||||
icon: 'IconSettingsAutomation',
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsNullable()
|
||||
trigger: WorkflowTrigger | null;
|
||||
|
||||
@@ -110,6 +111,7 @@ export class WorkflowVersionWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`Json object to provide steps`,
|
||||
icon: 'IconSettingsAutomation',
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsNullable()
|
||||
steps: WorkflowAction[] | null;
|
||||
|
||||
@@ -122,6 +124,7 @@ export class WorkflowVersionWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
options: WorkflowVersionStatusOptions,
|
||||
defaultValue: "'DRAFT'",
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
status: WorkflowVersionStatus;
|
||||
|
||||
@WorkspaceField({
|
||||
@@ -132,6 +135,7 @@ export class WorkflowVersionWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
icon: 'IconHierarchy2',
|
||||
defaultValue: 0,
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsSystem()
|
||||
position: number;
|
||||
|
||||
@@ -146,6 +150,7 @@ export class WorkflowVersionWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
SEARCH_FIELDS_FOR_WORKFLOW_VERSIONS,
|
||||
),
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsNullable()
|
||||
@WorkspaceIsSystem()
|
||||
@WorkspaceFieldIndex({ indexType: IndexType.GIN })
|
||||
@@ -162,6 +167,7 @@ export class WorkflowVersionWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
inverseSideFieldKey: 'versions',
|
||||
onDelete: RelationOnDeleteAction.CASCADE,
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsNullable()
|
||||
workflow: Relation<WorkflowWorkspaceEntity>;
|
||||
|
||||
@@ -177,6 +183,7 @@ export class WorkflowVersionWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
inverseSideTarget: () => WorkflowRunWorkspaceEntity,
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsNullable()
|
||||
runs: Relation<WorkflowRunWorkspaceEntity>;
|
||||
|
||||
@@ -189,6 +196,7 @@ export class WorkflowVersionWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
inverseSideTarget: () => FavoriteWorkspaceEntity,
|
||||
onDelete: RelationOnDeleteAction.CASCADE,
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsSystem()
|
||||
favorites: Relation<FavoriteWorkspaceEntity[]>;
|
||||
|
||||
@@ -201,6 +209,7 @@ export class WorkflowVersionWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
inverseSideFieldKey: 'targetWorkflowVersion',
|
||||
onDelete: RelationOnDeleteAction.CASCADE,
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
@WorkspaceIsSystem()
|
||||
timelineActivities: Relation<TimelineActivityWorkspaceEntity[]>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user