fix: broadcast timeline activities to live SSE subscriptions (#21104)
## Context Timeline activities never updated in real time. They were explicitly excluded from the database-event pipeline (formatTwentyOrmEventToDatabaseBatchEvent early-returned for the timelineActivity object), so no SSE event was ever broadcast, and the frontend timeline only refreshed on mount/manual refetch. ## Implementation Backend - Feat: Stop dropping timeline-activity events in formatTwentyOrmEventToDatabaseBatchEvent. Instead route them through EntityEventsToDbListener, which publishes them directly to live subscriptions (but still skipping webhook/audit handling). - Fix: Harden ObjectRecordEventPublisher: wrap nested-relation enrichment in try/catch so a failure broadcasts the event without relations instead of dropping it (logs a warning). - Fix: Skip unreadable relation targets in CommonSelectFieldsHelper when the role lacks canReadObjectRecords, preventing errors while computing selected fields. - Fix: Support MORPH_RELATION alongside RELATION in RLS row-level permission predicate matching (timeline activities use morph targets). Frontend - Feat: useTimelineActivities now registers the timeline query with the SSE system via useListenToEventsForQuery and refetches on incoming timeline-activity record operations. - Feat: Add a skip option to useListenToEventsForQuery so the listener isn't registered when the object has no timeline field. ## Test https://github.com/user-attachments/assets/ed1d1c66-d6ea-434d-ac9c-9b83d2b78338 Note: "UpdatedBy" seems to be listen to and visible in the timeline activity summary, this is probably a bug that we want to fix
This commit is contained in:
-8
@@ -7,7 +7,6 @@ import {
|
||||
ObjectRecordUpsertEvent,
|
||||
type ObjectRecordDiff,
|
||||
} from 'twenty-shared/database-events';
|
||||
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
|
||||
import {
|
||||
assertUnreachable,
|
||||
isDefined,
|
||||
@@ -47,13 +46,6 @@ export const formatTwentyOrmEventToDatabaseBatchEvent = <
|
||||
recordsAfter?: T[];
|
||||
recordsBefore?: T[];
|
||||
}): DatabaseBatchEventInput<T, DatabaseEventAction> | undefined => {
|
||||
if (
|
||||
objectMetadataItem.universalIdentifier ===
|
||||
STANDARD_OBJECTS.timelineActivity.universalIdentifier
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const objectMetadataNameSingular = objectMetadataItem.nameSingular;
|
||||
|
||||
let events: (
|
||||
|
||||
+4
-2
@@ -208,7 +208,8 @@ export const isRecordMatchingRLSRowLevelPermissionPredicate = ({
|
||||
objectFields.find((field) => field.name === filterKey) ??
|
||||
objectFields.find(
|
||||
(field) =>
|
||||
field.type === FieldMetadataType.RELATION &&
|
||||
(field.type === FieldMetadataType.RELATION ||
|
||||
field.type === FieldMetadataType.MORPH_RELATION) &&
|
||||
computeMorphOrRelationFieldJoinColumnName({ name: field.name }) ===
|
||||
filterKey,
|
||||
);
|
||||
@@ -411,7 +412,8 @@ export const isRecordMatchingRLSRowLevelPermissionPredicate = ({
|
||||
});
|
||||
});
|
||||
}
|
||||
case FieldMetadataType.RELATION: {
|
||||
case FieldMetadataType.RELATION:
|
||||
case FieldMetadataType.MORPH_RELATION: {
|
||||
const isJoinColumn =
|
||||
computeMorphOrRelationFieldJoinColumnName({
|
||||
name: objectMetadataField.name,
|
||||
|
||||
Reference in New Issue
Block a user