Use optimistic rendering when executing a workflow with manual trigger (#12695)

This PR adds optimistic rendering at two places:

- In the `runWorkflowVersion`, to create a workflow run entry as fast as
possible in the cache and render it immediately in the side panel.
- In the `ListenUpdatesEffect`, to be sure the cache is properly set; we
also need to set the record in the record store that's used in the
fields card.


## Before


https://github.com/user-attachments/assets/8b360ea9-c292-4e05-82a0-d2f12176bb6f

## After


https://github.com/user-attachments/assets/2d11023c-2ceb-4fa3-a951-187b9a0b5743

### With a slowed-down network


https://github.com/user-attachments/assets/7d2a592a-1ea7-455b-856f-bf3d9d905061

## Follow up

I will create next a PR to ensure the viewport is always set when we
know the dimensions of the nodes.
This commit is contained in:
Baptiste Devessier
2025-06-19 14:09:47 +02:00
committed by GitHub
parent a6b8830b91
commit dae282ca0f
13 changed files with 261 additions and 46 deletions

View File

@ -1,6 +1,18 @@
import { triggerUpdateRecordOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
import { getObjectTypename } from '@/object-record/cache/utils/getObjectTypename';
import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord';
import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache';
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { useOnDbEvent } from '@/subscription/hooks/useOnDbEvent';
import { useApolloClient } from '@apollo/client';
import { capitalize, isDefined } from 'twenty-shared/utils';
import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { DatabaseEventAction } from '~/generated/graphql';
type ListenRecordUpdatesEffectProps = {
@ -16,28 +28,70 @@ export const ListenRecordUpdatesEffect = ({
}: ListenRecordUpdatesEffectProps) => {
const apolloClient = useApolloClient();
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
});
const getRecordFromCache = useGetRecordFromCache({
objectNameSingular,
});
const { objectMetadataItems } = useObjectMetadataItems();
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const computedRecordGqlFields = generateDepthOneRecordGqlFields({
objectMetadataItem,
});
const setRecordInStore = useRecoilCallback(
({ set }) =>
(record: ObjectRecord) => {
set(recordStoreFamilyState(record.id), record);
},
[],
);
useOnDbEvent({
input: { recordId, action: DatabaseEventAction.UPDATED },
onData: (data) => {
const updatedRecord = data.onDbEvent.record;
const fieldsUpdater = listenedFields.reduce((acc, listenedField) => {
if (!isDefined(updatedRecord[listenedField])) {
return acc;
}
return {
...acc,
[listenedField]: () => updatedRecord[listenedField],
};
}, {});
apolloClient.cache.modify({
id: apolloClient.cache.identify({
__typename: capitalize(objectNameSingular),
id: recordId,
}),
fields: fieldsUpdater,
const cachedRecord = getRecordFromCache<ObjectRecord>(recordId);
const cachedRecordNode = getRecordNodeFromRecord<ObjectRecord>({
record: cachedRecord,
objectMetadataItem,
objectMetadataItems,
computeReferences: false,
});
const shouldHandleOptimisticCache =
isDefined(cachedRecordNode) && isDefined(cachedRecord);
if (shouldHandleOptimisticCache) {
const computedOptimisticRecord: ObjectRecord = {
...cachedRecord,
...updatedRecord,
id: recordId,
__typename: getObjectTypename(objectMetadataItem.nameSingular),
};
updateRecordFromCache({
objectMetadataItems,
objectMetadataItem,
cache: apolloClient.cache,
record: computedOptimisticRecord,
recordGqlFields: computedRecordGqlFields,
objectPermissionsByObjectMetadataId,
});
triggerUpdateRecordOptimisticEffect({
cache: apolloClient.cache,
objectMetadataItem,
currentRecord: cachedRecordNode,
updatedRecord: updatedRecord,
objectMetadataItems,
});
setRecordInStore(computedOptimisticRecord);
}
},
skip: listenedFields.length === 0,
});