Add delete and restore event handling for table and board#17489
Add delete and restore event handling for table and board#17489lucasbordeau merged 2 commits intomainfrom
Conversation
Greptile OverviewGreptile SummaryThis PR implements SSE event handling for soft-delete and restore operations in both table and board views. Key Changes:
Limitations:
Confidence Score: 5/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant Server as Twenty Server
participant SSE as SSE Connection
participant EventHandler as useTriggerOptimisticEffectFromSseEvents
participant DeleteHandler as useTriggerOptimisticEffectFromSseDeleteEvents
participant RestoreHandler as useTriggerOptimisticEffectFromSseRestoreEvents
participant Cache as Apollo Cache
participant BoardEffect as RecordBoardDataChangedEffect
participant TableEffect as RecordTableVirtualizedDataChangedEffect
participant Board as Board UI
participant Table as Table UI
Note over Server,Table: Delete Operation Flow
Server->>SSE: Emit DELETED event (before/after record state)
SSE->>EventHandler: objectRecordEvents[]
EventHandler->>EventHandler: Group by object type and event type
EventHandler->>DeleteHandler: DELETED events for object
DeleteHandler->>Cache: updateRecordFromCache (set deletedAt)
DeleteHandler->>Cache: triggerUpdateRecordOptimisticEffectByBatch
DeleteHandler->>Cache: refetchAggregateQueries (debounced 100ms)
DeleteHandler-->>EventHandler: Done
EventHandler->>EventHandler: dispatchObjectRecordOperationBrowserEvent
EventHandler->>BoardEffect: delete-one/delete-many operation
BoardEffect->>Board: removeRecordsFromBoard (filter IDs from groups)
EventHandler->>TableEffect: delete-one/delete-many operation
TableEffect->>Table: resetVirtualization (debounced 50ms, refetch)
Note over Server,Table: Restore Operation Flow
Server->>SSE: Emit RESTORED event (before/after record state)
SSE->>EventHandler: objectRecordEvents[]
EventHandler->>EventHandler: Group by object type and event type
EventHandler->>RestoreHandler: RESTORED events for object
RestoreHandler->>Cache: updateRecordFromCache (clear deletedAt)
RestoreHandler->>Cache: triggerUpdateRecordOptimisticEffectByBatch
RestoreHandler->>Cache: refetchAggregateQueries (debounced 100ms)
RestoreHandler-->>EventHandler: Done
EventHandler->>EventHandler: dispatchObjectRecordOperationBrowserEvent
EventHandler->>BoardEffect: restore-one/restore-many operation
BoardEffect->>BoardEffect: No-op (not yet implemented)
EventHandler->>TableEffect: restore-one/restore-many operation
TableEffect->>Table: resetVirtualization (debounced 50ms, refetch)
Note over Server,Cache: Server-Side RLS Filtering
Server->>Server: shouldIgnoreSoftDeleteDefaultFilter=true for DELETED/RESTORED
Server->>Server: isRecordMatchingRLSRowLevelPermissionPredicate
Server->>SSE: Only emit to subscribers with matching permissions
|
There was a problem hiding this comment.
1 issue found across 15 files
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them.
<file name="packages/twenty-server/src/engine/twenty-orm/utils/is-record-matching-rls-row-level-permission-predicate.util.ts">
<violation number="1" location="packages/twenty-server/src/engine/twenty-orm/utils/is-record-matching-rls-row-level-permission-predicate.util.ts:183">
P1: Leaf filters that explicitly target `deletedAt` can no longer match soft-deleted rows. The new guard rejects any leaf filter when `record.deletedAt` is defined, so queries like `{ deletedAt: { is: 'NOT_NULL' } }` always fail. Preserve the previous behavior by only rejecting when the filter doesn’t specify `deletedAt`.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| const shouldRejectMatchingBecauseRecordIsSoftDeleted = | ||
| isLeafFilter(filter) && | ||
| shouldTakeDeletedAtIntoAccount && | ||
| isDefined(record.deletedAt); |
There was a problem hiding this comment.
P1: Leaf filters that explicitly target deletedAt can no longer match soft-deleted rows. The new guard rejects any leaf filter when record.deletedAt is defined, so queries like { deletedAt: { is: 'NOT_NULL' } } always fail. Preserve the previous behavior by only rejecting when the filter doesn’t specify deletedAt.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/twenty-server/src/engine/twenty-orm/utils/is-record-matching-rls-row-level-permission-predicate.util.ts, line 183:
<comment>Leaf filters that explicitly target `deletedAt` can no longer match soft-deleted rows. The new guard rejects any leaf filter when `record.deletedAt` is defined, so queries like `{ deletedAt: { is: 'NOT_NULL' } }` always fail. Preserve the previous behavior by only rejecting when the filter doesn’t specify `deletedAt`.</comment>
<file context>
@@ -166,14 +172,21 @@ export const isRecordMatchingRLSRowLevelPermissionPredicate = ({
+ const shouldTakeDeletedAtIntoAccount =
+ shouldIgnoreSoftDeleteDefaultFilter !== true;
+
+ const shouldRejectMatchingBecauseRecordIsSoftDeleted =
+ isLeafFilter(filter) &&
+ shouldTakeDeletedAtIntoAccount &&
</file context>
| const shouldRejectMatchingBecauseRecordIsSoftDeleted = | |
| isLeafFilter(filter) && | |
| shouldTakeDeletedAtIntoAccount && | |
| isDefined(record.deletedAt); | |
| const shouldRejectMatchingBecauseRecordIsSoftDeleted = | |
| isLeafFilter(filter) && | |
| shouldTakeDeletedAtIntoAccount && | |
| isDefined(record.deletedAt) && | |
| filter.deletedAt === undefined; |
|
🚀 Preview Environment Ready! Your preview environment is available at: http://bore.pub:3471 This environment will automatically shut down when the PR is closed or after 5 hours. |
|
Hey @lucasbordeau! After you've done the QA of your Pull Request, you can mark it as done here. Thank you! |
1 similar comment
|
Hey @lucasbordeau! After you've done the QA of your Pull Request, you can mark it as done here. Thank you! |
This PR adds what is required to handle soft-delete and restore SSE events in virtualized table and board.
Restore is not handled in board for now as it requires respecting sorts when inserting record ids.
Since virtualized table is refetching small chunks, we just refetch for now.
The long term goal is to handle event handling without refetching in all main components, and also handle SSE events that have the same origin that the current tab. But for now we implement what is easily doable.
QA
Delete between table and board (delete only) :
Enregistrement.de.l.ecran.2026-01-27.a.17.57.38.mov
Delete and restore between table and table :
Enregistrement.de.l.ecran.2026-01-27.a.17.58.39.mov