Activity cache injection (#3791)

* WIP

* Minor fixes

* Added TODO

* Fix post merge

* Fix

* Fixed warnings

* Fixed comments

* Fixed comments

* Fixed naming

* Removed comment

* WIP

* WIP 2

* Finished working version

* Fixes

* Fixed typing

* Fixes

* Fixes

* Fixes

* Naming fixes

* WIP

* Fix import

* WIP

* Working version on title

* Fixed create record id overwrite

* Removed unecessary callback

* Masterpiece

* Fixed delete on click outside drawer or delete

* Cleaned

* Cleaned

* Cleaned

* Minor fixes

* Fixes

* Fixed naming

* WIP

* Fix

* Fixed create from target inline cell

* Removed console.log

* Fixed delete activity optimistic effect

* Fixed no title

* Fixed debounce and title body creation

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Lucas Bordeau
2024-02-09 14:51:30 +01:00
committed by GitHub
parent 9ceff84bbf
commit cca72da708
87 changed files with 2195 additions and 1058 deletions

View File

@ -1,89 +0,0 @@
import { useApolloClient } from '@apollo/client';
import { isNonEmptyString } from '@sniptt/guards';
import { makeTimelineActivitiesQueryVariables } from '@/activities/timeline/utils/makeTimelineActivitiesQueryVariables';
import { Activity } from '@/activities/types/Activity';
import { ActivityTarget } from '@/activities/types/ActivityTarget';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { getRecordConnectionFromRecords } from '@/object-record/cache/utils/getRecordConnectionFromRecords';
import { getRecordsFromRecordConnection } from '@/object-record/cache/utils/getRecordsFromRecordConnection';
import { ObjectRecordConnection } from '@/object-record/types/ObjectRecordConnection';
export const useInjectIntoTimelineActivitiesQuery = () => {
const apolloClient = useApolloClient();
const {
objectMetadataItem: objectMetadataItemActivity,
findManyRecordsQuery: findManyActivitiesQuery,
} = useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.Activity,
});
const injectIntoTimelineActivitiesQuery = ({
activityTargets,
activityToInject,
}: {
activityTargets: ActivityTarget[];
activityToInject: Activity;
}) => {
const activityIds = activityTargets
?.map((activityTarget) => activityTarget.activityId)
.filter(isNonEmptyString);
const timelineActivitiesQueryVariables =
makeTimelineActivitiesQueryVariables({
activityIds,
});
const exitistingActivitiesQueryResult = apolloClient.readQuery({
query: findManyActivitiesQuery,
variables: timelineActivitiesQueryVariables,
});
const extistingActivities = exitistingActivitiesQueryResult
? getRecordsFromRecordConnection({
recordConnection: exitistingActivitiesQueryResult[
objectMetadataItemActivity.namePlural
] as ObjectRecordConnection<Activity>,
})
: [];
const newActivity = {
...activityToInject,
__typename: 'Activity',
};
const newActivitiesSortedAsActivitiesQuery = [
newActivity,
...extistingActivities,
];
const newActivityIdsSortedAsActivityTargetsQuery = [
...extistingActivities,
newActivity,
].map((activity) => activity.id);
const newTimelineActivitiesQueryVariables =
makeTimelineActivitiesQueryVariables({
activityIds: newActivityIdsSortedAsActivityTargetsQuery,
});
const newActivityConnectionForCache = getRecordConnectionFromRecords({
objectNameSingular: CoreObjectNameSingular.Activity,
records: newActivitiesSortedAsActivitiesQuery,
});
apolloClient.writeQuery({
query: findManyActivitiesQuery,
variables: newTimelineActivitiesQueryVariables,
data: {
[objectMetadataItemActivity.namePlural]: newActivityConnectionForCache,
},
});
};
return {
injectIntoTimelineActivitiesNextQuery: injectIntoTimelineActivitiesQuery,
};
};

View File

@ -0,0 +1,124 @@
import { isNonEmptyString } from '@sniptt/guards';
import { makeTimelineActivitiesQueryVariables } from '@/activities/timeline/utils/makeTimelineActivitiesQueryVariables';
import { Activity } from '@/activities/types/Activity';
import { ActivityTarget } from '@/activities/types/ActivityTarget';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import { getActivityTargetObjectFieldIdName } from '@/activities/utils/getTargetObjectFilterFieldName';
import { useObjectMetadataItemOnly } from '@/object-metadata/hooks/useObjectMetadataItemOnly';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useReadFindManyRecordsQueryInCache } from '@/object-record/cache/hooks/useReadFindManyRecordsQueryInCache';
import { useUpsertFindManyRecordsQueryInCache } from '@/object-record/cache/hooks/useUpsertFindManyRecordsQueryInCache';
export const useInjectIntoTimelineActivitiesQueryAfterDrawerMount = () => {
const { objectMetadataItem: objectMetadataItemActivity } =
useObjectMetadataItemOnly({
objectNameSingular: CoreObjectNameSingular.Activity,
});
const {
upsertFindManyRecordsQueryInCache: overwriteFindManyActivitiesInCache,
} = useUpsertFindManyRecordsQueryInCache({
objectMetadataItem: objectMetadataItemActivity,
});
const { objectMetadataItem: objectMetadataItemActivityTarget } =
useObjectMetadataItemOnly({
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
});
const {
readFindManyRecordsQueryInCache: readFindManyActivityTargetsQueryInCache,
} = useReadFindManyRecordsQueryInCache({
objectMetadataItem: objectMetadataItemActivityTarget,
});
const {
readFindManyRecordsQueryInCache: readFindManyActivitiesQueryInCache,
} = useReadFindManyRecordsQueryInCache({
objectMetadataItem: objectMetadataItemActivity,
});
const {
upsertFindManyRecordsQueryInCache:
overwriteFindManyActivityTargetsQueryInCache,
} = useUpsertFindManyRecordsQueryInCache({
objectMetadataItem: objectMetadataItemActivityTarget,
});
const injectIntoTimelineActivitiesQueryAfterDrawerMount = ({
activityToInject,
activityTargetsToInject,
timelineTargetableObject,
}: {
activityToInject: Activity;
activityTargetsToInject: ActivityTarget[];
timelineTargetableObject: ActivityTargetableObject;
}) => {
const newActivity = {
...activityToInject,
__typename: 'Activity',
};
const targetObjectFieldName = getActivityTargetObjectFieldIdName({
nameSingular: timelineTargetableObject.targetObjectNameSingular,
});
const activitiyTargetsForTargetableObjectQueryVariables = {
filter: {
[targetObjectFieldName]: {
eq: timelineTargetableObject.id,
},
},
};
const existingActivityTargetsForTargetableObject =
readFindManyActivityTargetsQueryInCache({
queryVariables: activitiyTargetsForTargetableObjectQueryVariables,
});
const newActivityTargetsForTargetableObject = [
...existingActivityTargetsForTargetableObject,
...activityTargetsToInject,
];
const existingActivityIds = existingActivityTargetsForTargetableObject
?.map((activityTarget) => activityTarget.activityId)
.filter(isNonEmptyString);
const timelineActivitiesQueryVariablesBeforeDrawerMount =
makeTimelineActivitiesQueryVariables({
activityIds: existingActivityIds,
});
const existingActivities = readFindManyActivitiesQueryInCache({
queryVariables: timelineActivitiesQueryVariablesBeforeDrawerMount,
});
const activityIdsAfterDrawerMount = [
...existingActivityIds,
newActivity.id,
];
const timelineActivitiesQueryVariablesAfterDrawerMount =
makeTimelineActivitiesQueryVariables({
activityIds: activityIdsAfterDrawerMount,
});
overwriteFindManyActivityTargetsQueryInCache({
objectRecordsToOverwrite: newActivityTargetsForTargetableObject,
queryVariables: activitiyTargetsForTargetableObjectQueryVariables,
});
const newActivities = [newActivity, ...existingActivities];
overwriteFindManyActivitiesInCache({
objectRecordsToOverwrite: newActivities,
queryVariables: timelineActivitiesQueryVariablesAfterDrawerMount,
});
};
return {
injectIntoTimelineActivitiesQueryAfterDrawerMount,
};
};

View File

@ -1,7 +1,7 @@
import { useEffect, useState } from 'react';
import { isNonEmptyArray, isNonEmptyString } from '@sniptt/guards';
import { useActivityTargets } from '@/activities/hooks/useActivityTargets';
import { useActivityTargetsForTargetableObject } from '@/activities/hooks/useActivityTargetsForTargetableObject';
import { makeTimelineActivitiesQueryVariables } from '@/activities/timeline/utils/makeTimelineActivitiesQueryVariables';
import { Activity } from '@/activities/types/Activity';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
@ -17,14 +17,12 @@ export const useTimelineActivities = ({
activityTargets,
loadingActivityTargets,
initialized: initializedActivityTargets,
} = useActivityTargets({
} = useActivityTargetsForTargetableObject({
targetableObject,
});
const [initialized, setInitialized] = useState(false);
const [activities, setActivities] = useState<Activity[]>([]);
const activityIds = activityTargets
?.map((activityTarget) => activityTarget.activityId)
.filter(isNonEmptyString);
@ -35,7 +33,7 @@ export const useTimelineActivities = ({
},
);
const { records: activitiesFromRequest, loading: loadingActivities } =
const { records: activities, loading: loadingActivities } =
useFindManyRecords<Activity>({
skip: loadingActivityTargets || !isNonEmptyArray(activityTargets),
objectNameSingular: CoreObjectNameSingular.Activity,
@ -48,12 +46,6 @@ export const useTimelineActivities = ({
},
});
useEffect(() => {
if (!loadingActivities) {
setActivities(activitiesFromRequest);
}
}, [activitiesFromRequest, loadingActivities]);
const noActivityTargets =
initializedActivityTargets && !isNonEmptyArray(activityTargets);