Feat/activity optimistic activities (#4009)
* Fix naming * Fixed cache.evict bug for relation target deletion * Fixed cascade delete activity targets * Working version * Fix * fix * WIP * Fixed optimistic effect target inline cell * Removed openCreateActivityDrawer v1 * Ok for timeline * Removed console.log * Fix update record optimistic effect * Refactored activity queries into useActivities for everything * Fixed bugs * Cleaned * Fix lint --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -11,17 +11,19 @@ export const isObjectRecordConnection = (
|
||||
objectNameSingular,
|
||||
)}Connection`;
|
||||
const objectEdgeTypeName = `${capitalize(objectNameSingular)}Edge`;
|
||||
|
||||
const objectConnectionSchema = z.object({
|
||||
__typename: z.literal(objectConnectionTypeName),
|
||||
__typename: z.literal(objectConnectionTypeName).optional(),
|
||||
edges: z.array(
|
||||
z.object({
|
||||
__typename: z.literal(objectEdgeTypeName),
|
||||
__typename: z.literal(objectEdgeTypeName).optional(),
|
||||
node: z.object({
|
||||
id: z.string().uuid(),
|
||||
}),
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
const connectionValidation = objectConnectionSchema.safeParse(value);
|
||||
|
||||
return connectionValidation.success;
|
||||
|
||||
@ -32,29 +32,25 @@ export const triggerDetachRelationOptimisticEffect = ({
|
||||
targetRecordFieldValue,
|
||||
{ isReference, readField },
|
||||
) => {
|
||||
const isRelationTargetFieldAnObjectRecordConnection =
|
||||
isCachedObjectRecordConnection(
|
||||
sourceObjectNameSingular,
|
||||
targetRecordFieldValue,
|
||||
);
|
||||
|
||||
if (isRelationTargetFieldAnObjectRecordConnection) {
|
||||
const relationTargetFieldEdgesWithoutRelationSourceRecordToDetach =
|
||||
targetRecordFieldValue.edges.filter(
|
||||
({ node }) => readField('id', node) !== sourceRecordId,
|
||||
);
|
||||
|
||||
return {
|
||||
...targetRecordFieldValue,
|
||||
edges: relationTargetFieldEdgesWithoutRelationSourceRecordToDetach,
|
||||
};
|
||||
}
|
||||
|
||||
const isRelationTargetFieldASingleObjectRecord = isReference(
|
||||
const isRecordConnection = isCachedObjectRecordConnection(
|
||||
sourceObjectNameSingular,
|
||||
targetRecordFieldValue,
|
||||
);
|
||||
|
||||
if (isRelationTargetFieldASingleObjectRecord) {
|
||||
if (isRecordConnection) {
|
||||
const nextEdges = targetRecordFieldValue.edges.filter(
|
||||
({ node }) => readField('id', node) !== sourceRecordId,
|
||||
);
|
||||
|
||||
return {
|
||||
...targetRecordFieldValue,
|
||||
edges: nextEdges,
|
||||
};
|
||||
}
|
||||
|
||||
const isSingleReference = isReference(targetRecordFieldValue);
|
||||
|
||||
if (isSingleReference) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@ -45,41 +45,35 @@ export const triggerUpdateRecordOptimisticEffect = ({
|
||||
rootQueryCachedResponse,
|
||||
{ DELETE, readField, storeFieldName, toReference },
|
||||
) => {
|
||||
const rootQueryCachedResponseIsNotACachedObjectRecordConnection =
|
||||
!isCachedObjectRecordConnection(
|
||||
objectMetadataItem.nameSingular,
|
||||
rootQueryCachedResponse,
|
||||
);
|
||||
const shouldSkip = !isCachedObjectRecordConnection(
|
||||
objectMetadataItem.nameSingular,
|
||||
rootQueryCachedResponse,
|
||||
);
|
||||
|
||||
if (rootQueryCachedResponseIsNotACachedObjectRecordConnection) {
|
||||
if (shouldSkip) {
|
||||
return rootQueryCachedResponse;
|
||||
}
|
||||
|
||||
const rootQueryCachedObjectRecordConnection = rootQueryCachedResponse;
|
||||
const rootQueryConnection = rootQueryCachedResponse;
|
||||
|
||||
const { fieldArguments: rootQueryVariables } =
|
||||
parseApolloStoreFieldName<CachedObjectRecordQueryVariables>(
|
||||
storeFieldName,
|
||||
);
|
||||
|
||||
const rootQueryCurrentCachedRecordEdges =
|
||||
readField<CachedObjectRecordEdge[]>(
|
||||
'edges',
|
||||
rootQueryCachedObjectRecordConnection,
|
||||
) ?? [];
|
||||
const rootQueryCurrentEdges =
|
||||
readField<CachedObjectRecordEdge[]>('edges', rootQueryConnection) ??
|
||||
[];
|
||||
|
||||
let rootQueryNextCachedRecordEdges = [
|
||||
...rootQueryCurrentCachedRecordEdges,
|
||||
];
|
||||
let rootQueryNextEdges = [...rootQueryCurrentEdges];
|
||||
|
||||
const rootQueryFilter = rootQueryVariables?.filter;
|
||||
const rootQueryOrderBy = rootQueryVariables?.orderBy;
|
||||
const rootQueryLimit = rootQueryVariables?.first;
|
||||
|
||||
const shouldTestThatUpdatedRecordMatchesThisRootQueryFilter =
|
||||
isDefined(rootQueryFilter);
|
||||
const shouldTryToMatchFilter = isDefined(rootQueryFilter);
|
||||
|
||||
if (shouldTestThatUpdatedRecordMatchesThisRootQueryFilter) {
|
||||
if (shouldTryToMatchFilter) {
|
||||
const updatedRecordMatchesThisRootQueryFilter =
|
||||
isRecordMatchingFilter({
|
||||
record: updatedRecord,
|
||||
@ -88,24 +82,27 @@ export const triggerUpdateRecordOptimisticEffect = ({
|
||||
});
|
||||
|
||||
const updatedRecordIndexInRootQueryEdges =
|
||||
rootQueryCurrentCachedRecordEdges.findIndex(
|
||||
rootQueryCurrentEdges.findIndex(
|
||||
(cachedEdge) =>
|
||||
readField('id', cachedEdge.node) === updatedRecord.id,
|
||||
);
|
||||
|
||||
const updatedRecordFoundInRootQueryEdges =
|
||||
updatedRecordIndexInRootQueryEdges > -1;
|
||||
|
||||
const updatedRecordShouldBeAddedToRootQueryEdges =
|
||||
updatedRecordMatchesThisRootQueryFilter &&
|
||||
updatedRecordIndexInRootQueryEdges === -1;
|
||||
!updatedRecordFoundInRootQueryEdges;
|
||||
|
||||
const updatedRecordShouldBeRemovedFromRootQueryEdges =
|
||||
updatedRecordMatchesThisRootQueryFilter &&
|
||||
updatedRecordIndexInRootQueryEdges === -1;
|
||||
!updatedRecordMatchesThisRootQueryFilter &&
|
||||
updatedRecordFoundInRootQueryEdges;
|
||||
|
||||
if (updatedRecordShouldBeAddedToRootQueryEdges) {
|
||||
const updatedRecordNodeReference = toReference(updatedRecord);
|
||||
|
||||
if (isDefined(updatedRecordNodeReference)) {
|
||||
rootQueryNextCachedRecordEdges.push({
|
||||
rootQueryNextEdges.push({
|
||||
__typename: objectEdgeTypeName,
|
||||
node: updatedRecordNodeReference,
|
||||
cursor: '',
|
||||
@ -114,18 +111,15 @@ export const triggerUpdateRecordOptimisticEffect = ({
|
||||
}
|
||||
|
||||
if (updatedRecordShouldBeRemovedFromRootQueryEdges) {
|
||||
rootQueryNextCachedRecordEdges.splice(
|
||||
updatedRecordIndexInRootQueryEdges,
|
||||
1,
|
||||
);
|
||||
rootQueryNextEdges.splice(updatedRecordIndexInRootQueryEdges, 1);
|
||||
}
|
||||
}
|
||||
|
||||
const nextRootQueryEdgesShouldBeSorted = isDefined(rootQueryOrderBy);
|
||||
const rootQueryNextEdgesShouldBeSorted = isDefined(rootQueryOrderBy);
|
||||
|
||||
if (nextRootQueryEdgesShouldBeSorted) {
|
||||
rootQueryNextCachedRecordEdges = sortCachedObjectEdges({
|
||||
edges: rootQueryNextCachedRecordEdges,
|
||||
if (rootQueryNextEdgesShouldBeSorted) {
|
||||
rootQueryNextEdges = sortCachedObjectEdges({
|
||||
edges: rootQueryNextEdges,
|
||||
orderBy: rootQueryOrderBy,
|
||||
readCacheField: readField,
|
||||
});
|
||||
@ -158,12 +152,12 @@ export const triggerUpdateRecordOptimisticEffect = ({
|
||||
// the query's result.
|
||||
// In this case, invalidate the cache entry so it can be re-fetched.
|
||||
const rootQueryCurrentCachedRecordEdgesLengthIsAtLimit =
|
||||
rootQueryCurrentCachedRecordEdges.length === rootQueryLimit;
|
||||
rootQueryCurrentEdges.length === rootQueryLimit;
|
||||
|
||||
// If next edges length is under limit, then we can wait for the network response and merge the result
|
||||
// then in the merge function we could implement this mechanism to limit the number of edges in the cache
|
||||
const rootQueryNextCachedRecordEdgesLengthIsUnderLimit =
|
||||
rootQueryNextCachedRecordEdges.length < rootQueryLimit;
|
||||
rootQueryNextEdges.length < rootQueryLimit;
|
||||
|
||||
const shouldDeleteRootQuerySoItCanBeRefetched =
|
||||
rootQueryCurrentCachedRecordEdgesLengthIsAtLimit &&
|
||||
@ -174,16 +168,16 @@ export const triggerUpdateRecordOptimisticEffect = ({
|
||||
}
|
||||
|
||||
const rootQueryNextCachedRecordEdgesLengthIsAboveRootQueryLimit =
|
||||
rootQueryNextCachedRecordEdges.length > rootQueryLimit;
|
||||
rootQueryNextEdges.length > rootQueryLimit;
|
||||
|
||||
if (rootQueryNextCachedRecordEdgesLengthIsAboveRootQueryLimit) {
|
||||
rootQueryNextCachedRecordEdges.splice(rootQueryLimit);
|
||||
rootQueryNextEdges.splice(rootQueryLimit);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...rootQueryCachedObjectRecordConnection,
|
||||
edges: rootQueryNextCachedRecordEdges,
|
||||
...rootQueryConnection,
|
||||
edges: rootQueryNextEdges,
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
@ -6,7 +6,7 @@ import { triggerAttachRelationOptimisticEffect } from '@/apollo/optimistic-effec
|
||||
import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect';
|
||||
import { triggerDetachRelationOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDetachRelationOptimisticEffect';
|
||||
import { CachedObjectRecord } from '@/apollo/types/CachedObjectRecord';
|
||||
import { CORE_OBJECT_NAMES_TO_DELETE_ON_TRIGGER_RELATION_DETACH as CORE_OBJECT_NAMES_TO_DELETE_ON_OPTIMISTIC_RELATION_DETACH } from '@/apollo/types/coreObjectNamesToDeleteOnRelationDetach';
|
||||
import { CORE_OBJECT_NAMES_TO_DELETE_ON_TRIGGER_RELATION_DETACH } from '@/apollo/types/coreObjectNamesToDeleteOnRelationDetach';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { ObjectRecordConnection } from '@/object-record/types/ObjectRecordConnection';
|
||||
@ -74,6 +74,8 @@ export const triggerUpdateRelationsOptimisticEffect = ({
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: replace this by a relation type check, if it's one to many,
|
||||
// it's an object record connection (we can still check it though as a safeguard)
|
||||
const currentFieldValueOnSourceRecordIsARecordConnection =
|
||||
isObjectRecordConnection(
|
||||
targetObjectMetadataItem.nameSingular,
|
||||
@ -104,12 +106,14 @@ export const triggerUpdateRelationsOptimisticEffect = ({
|
||||
isDefined(currentSourceRecord) && targetRecordsToDetachFrom.length > 0;
|
||||
|
||||
if (shouldDetachSourceFromAllTargets) {
|
||||
const shouldStartByDeletingRelationTargetRecordsFromCache =
|
||||
CORE_OBJECT_NAMES_TO_DELETE_ON_OPTIMISTIC_RELATION_DETACH.includes(
|
||||
// TODO: see if we can de-hardcode this, put cascade delete in relation metadata item
|
||||
// Instead of hardcoding it here
|
||||
const shouldCascadeDeleteTargetRecords =
|
||||
CORE_OBJECT_NAMES_TO_DELETE_ON_TRIGGER_RELATION_DETACH.includes(
|
||||
targetObjectMetadataItem.nameSingular as CoreObjectNameSingular,
|
||||
);
|
||||
|
||||
if (shouldStartByDeletingRelationTargetRecordsFromCache) {
|
||||
if (shouldCascadeDeleteTargetRecords) {
|
||||
triggerDeleteRecordsOptimisticEffect({
|
||||
cache,
|
||||
objectMetadataItem: targetObjectMetadataItem,
|
||||
|
||||
Reference in New Issue
Block a user