Extend live update coverage: workflow draft updates and side-panel records (#23830)
Stacked on #23811. Closes the two remaining live-update coverage gaps found while auditing record-seeded editing surfaces. ## Workflow diagram misses updates to the current draft version `WorkflowSSESubscribeEffect` triggered a content refetch only on `create-one` of a workflow version (new draft created) and on SSE reconnection. Step and trigger edits on the existing draft arrive as `update-one` events and left the open diagram stale until a refresh, which is the common case when the AI chat or a teammate edits a draft workflow. Refetch on `update-one`/`update-many` too. Local workflow mutations do not dispatch these browser events (they only originate from SSE deliveries), and an own-persist echo reseeds the diagram with the state it already shows, so this does not fight local editing. ## Side-panel records receive no SSE events `SidePanelRecordPage` registered no SSE query, so a record opened in the side panel (notes and tasks most commonly) got no events for itself unless another surface happened to subscribe to a matching query. With #23811's rich text adoption this mattered doubly: events could not reach the editor at all. Register the record query like `RecordShowPage` does. The subscribe effect takes a `queryScope` so the record page and side panel keep independent registrations when they display the same record, and closing one surface does not unsubscribe the other. --- _Generated by [Claude Code](https://claude.ai/code/session_018nGvGhFahw1pcefb3P4iCk)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23830?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
This commit is contained in:
+6
-1
@@ -3,13 +3,18 @@ import { useListenToEventsForQuery } from '@/sse-db-event/hooks/useListenToEvent
|
||||
type RecordShowPageSSESubscribeEffectProps = {
|
||||
objectNameSingular: string;
|
||||
recordId: string;
|
||||
// The record page and the side panel can display the same record at the
|
||||
// same time; distinct scopes keep their query registrations independent so
|
||||
// closing one surface does not unsubscribe the other.
|
||||
queryScope?: 'record-show' | 'side-panel-record';
|
||||
};
|
||||
|
||||
export const RecordShowPageSSESubscribeEffect = ({
|
||||
objectNameSingular,
|
||||
recordId,
|
||||
queryScope = 'record-show',
|
||||
}: RecordShowPageSSESubscribeEffectProps) => {
|
||||
const queryId = `record-show-${objectNameSingular}-${recordId}`;
|
||||
const queryId = `${queryScope}-${objectNameSingular}-${recordId}`;
|
||||
|
||||
useListenToEventsForQuery({
|
||||
queryId,
|
||||
|
||||
+6
@@ -7,6 +7,7 @@ import { ContextStoreComponentInstanceContext } from '@/context-store/states/con
|
||||
import { INFORMATION_BANNER_HEIGHT } from '@/information-banner/constants/InformationBannerHeight';
|
||||
import { RecordComponentInstanceContextsWrapper } from '@/object-record/components/RecordComponentInstanceContextsWrapper';
|
||||
import { PageLayoutRecordPageRenderer } from '@/object-record/record-show/components/PageLayoutRecordPageRenderer';
|
||||
import { RecordShowPageSSESubscribeEffect } from '@/object-record/record-show/components/RecordShowPageSSESubscribeEffect';
|
||||
import { useRecordShowPage } from '@/object-record/record-show/hooks/useRecordShowPage';
|
||||
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
|
||||
import { useComponentInstanceStateContext } from '@/ui/utilities/state/component-state/hooks/useComponentInstanceStateContext';
|
||||
@@ -88,6 +89,11 @@ export const SidePanelRecordPage = () => {
|
||||
}}
|
||||
isInSidePanel
|
||||
/>
|
||||
<RecordShowPageSSESubscribeEffect
|
||||
objectNameSingular={objectNameSingular}
|
||||
recordId={objectRecordId}
|
||||
queryScope="side-panel-record"
|
||||
/>
|
||||
</TimelineActivityContext.Provider>
|
||||
</StyledSidePanelRecord>
|
||||
</CommandMenuComponentInstanceContext.Provider>
|
||||
|
||||
+67
-4
@@ -1,11 +1,14 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { CoreObjectNameSingular } from 'twenty-shared/types';
|
||||
import { useListenToObjectRecordOperationBrowserEvent } from '@/browser-event/hooks/useListenToObjectRecordOperationBrowserEvent';
|
||||
import { type ObjectRecordOperationBrowserEventDetail } from '@/browser-event/types/ObjectRecordOperationBrowserEventDetail';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
|
||||
import { useListenToEventsForQuery } from '@/sse-db-event/hooks/useListenToEventsForQuery';
|
||||
import { useSetAtomFamilyState } from '@/ui/utilities/state/jotai/hooks/useSetAtomFamilyState';
|
||||
import { shouldWorkflowRefetchRequestFamilyState } from '@/workflow/states/shouldWorkflowRefetchRequestFamilyState';
|
||||
import { CoreObjectNameSingular } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const WorkflowSSESubscribeEffect = ({
|
||||
workflowId,
|
||||
@@ -28,6 +31,23 @@ export const WorkflowSSESubscribeEffect = ({
|
||||
setShouldWorkflowRefetchRequest(true);
|
||||
}, [setShouldWorkflowRefetchRequest]);
|
||||
|
||||
// Subset of the workflow query the page already runs, so the version ids
|
||||
// are served from the Apollo cache without an extra request.
|
||||
const { record: workflowWithVersionIds } = useFindOneRecord<{
|
||||
__typename: string;
|
||||
id: string;
|
||||
versions: Array<{ id: string }>;
|
||||
}>({
|
||||
objectNameSingular: CoreObjectNameSingular.Workflow,
|
||||
objectRecordId: workflowId,
|
||||
recordGqlFields: {
|
||||
id: true,
|
||||
versions: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
useListenToEventsForQuery({
|
||||
queryId,
|
||||
operationSignature: {
|
||||
@@ -41,10 +61,53 @@ export const WorkflowSSESubscribeEffect = ({
|
||||
onSseReconnected: requestWorkflowRefetch,
|
||||
});
|
||||
|
||||
// Creations cover new draft versions; updates cover step and trigger edits
|
||||
// on the current draft (the AI chat, another user, another tab). Local
|
||||
// workflow mutations do not dispatch these events, only SSE deliveries do,
|
||||
// and refetching on an own-persist echo reseeds the diagram with the state
|
||||
// it already shows.
|
||||
const handleWorkflowVersionOperationBrowserEvent = useCallback(
|
||||
(detail: ObjectRecordOperationBrowserEventDetail) => {
|
||||
if (detail.operation.type === 'create-one') {
|
||||
requestWorkflowRefetch();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const updateInputs =
|
||||
detail.operation.type === 'update-one'
|
||||
? [detail.operation.result.updateInput]
|
||||
: detail.operation.type === 'update-many'
|
||||
? detail.operation.result.updateInputs
|
||||
: [];
|
||||
|
||||
const workflowVersionIds = workflowWithVersionIds?.versions?.map(
|
||||
(version) => version.id,
|
||||
);
|
||||
|
||||
// Without the version mapping, refetch rather than risk a stale diagram.
|
||||
if (!isDefined(workflowVersionIds)) {
|
||||
requestWorkflowRefetch();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
updateInputs.some((updateInput) =>
|
||||
workflowVersionIds.includes(updateInput.recordId),
|
||||
)
|
||||
) {
|
||||
requestWorkflowRefetch();
|
||||
}
|
||||
},
|
||||
[workflowWithVersionIds, requestWorkflowRefetch],
|
||||
);
|
||||
|
||||
useListenToObjectRecordOperationBrowserEvent({
|
||||
onObjectRecordOperationBrowserEvent: requestWorkflowRefetch,
|
||||
onObjectRecordOperationBrowserEvent:
|
||||
handleWorkflowVersionOperationBrowserEvent,
|
||||
objectMetadataItemId: workflowVersionMetadataItem.id,
|
||||
operationTypes: ['create-one'],
|
||||
operationTypes: ['create-one', 'update-one', 'update-many'],
|
||||
});
|
||||
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user